Merge pull request #95 from sailpoint-oss/V2.0

CLI V2.0
This commit is contained in:
Luke Hagar
2023-09-11 15:59:58 -05:00
committed by GitHub
113 changed files with 3281 additions and 729 deletions

3
.gitignore vendored
View File

@@ -5,6 +5,8 @@
transform_files transform_files
search_results search_results
spconfig-exports spconfig-exports
reports
workflows
# CLI binary # CLI binary
sailpoint-cli sailpoint-cli
@@ -17,3 +19,4 @@ assets/demo_update.json
cmd/transform/test_data/test_create.json cmd/transform/test_data/test_create.json
cmd/transform/test_data/test_update.json cmd/transform/test_data/test_update.json
example_transforms example_transforms
config.json

34
cmd/cluster/cluster.go Normal file
View File

@@ -0,0 +1,34 @@
package cluster
import (
_ "embed"
"github.com/sailpoint-oss/sailpoint-cli/cmd/cluster/logConfig"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra"
)
//go:embed cluster.md
var clusterHelp string
func NewClusterCommand() *cobra.Command {
help := util.ParseHelp(clusterHelp)
cmd := &cobra.Command{
Use: "cluster",
Short: "Manage Clusters in IdentityNow",
Long: help.Long,
Example: help.Example,
Aliases: []string{"cl"},
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
cmd.AddCommand(
newListCommand(),
newGetCommand(),
logConfig.NewLogCommand(),
)
return cmd
}

19
cmd/cluster/cluster.md Normal file
View File

@@ -0,0 +1,19 @@
==Long==
# Cluster
Manage SailPoint IdentityNow Clusters
## API Reference:
- https://developer.sailpoint.com/idn/api/beta/managed-clusters
====
==Example==
```bash
sail cluster list
sail cluster get {cluster-id}
sail cluster get {cluster-id} {cluster-id} ...
sail cluster log
```
====

52
cmd/cluster/get.go Normal file
View File

@@ -0,0 +1,52 @@
package cluster
import (
"context"
_ "embed"
"github.com/sailpoint-oss/golang-sdk/beta"
"github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/sdk"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra"
)
//go:embed get.md
var getHelp string
func newGetCommand() *cobra.Command {
help := util.ParseHelp(getHelp)
cmd := &cobra.Command{
Use: "get",
Short: "Get a Cluster from IdentityNow",
Long: help.Long,
Example: help.Example,
Aliases: []string{"get"},
RunE: func(cmd *cobra.Command, args []string) error {
apiClient, err := config.InitAPIClient()
if err != nil {
return err
}
if len(args) > 0 {
var output []*beta.ManagedCluster
for _, id := range args {
clusters, resp, clustersErr := apiClient.Beta.ManagedClustersApi.GetManagedCluster(context.TODO(), id).Execute()
if clustersErr != nil {
return sdk.HandleSDKError(resp, clustersErr)
}
output = append(output, clusters)
}
cmd.Println(util.PrettyPrint(output))
} else {
cmd.Help()
}
return nil
},
}
return cmd
}

16
cmd/cluster/get.md Normal file
View File

@@ -0,0 +1,16 @@
==Long==
# Get
Get a clusters configuration from IdentityNow
## API References:
- https://developer.sailpoint.com/idn/api/beta/get-managed-cluster
====
==Example==
```bash
sail cluster get {cluster-id}
sail cluster get {cluster-id} {cluster-id} ...
```
====

52
cmd/cluster/list.go Normal file
View File

@@ -0,0 +1,52 @@
package cluster
import (
"context"
_ "embed"
sailpoint "github.com/sailpoint-oss/golang-sdk"
"github.com/sailpoint-oss/golang-sdk/beta"
"github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/output"
"github.com/sailpoint-oss/sailpoint-cli/internal/sdk"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra"
)
//go:embed list.md
var listHelp string
func newListCommand() *cobra.Command {
help := util.ParseHelp(listHelp)
cmd := &cobra.Command{
Use: "list",
Short: "List the Clusters configured in IdentityNow",
Long: help.Long,
Example: help.Example,
Aliases: []string{"ls"},
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
apiClient, err := config.InitAPIClient()
if err != nil {
return err
}
clusters, resp, clustersErr := sailpoint.PaginateWithDefaults[beta.ManagedCluster](apiClient.Beta.ManagedClustersApi.GetManagedClusters(context.TODO()))
if clustersErr != nil {
return sdk.HandleSDKError(resp, clustersErr)
}
var entries [][]string
for _, cluster := range clusters {
entries = append(entries, []string{*cluster.Name, *cluster.Org, cluster.Id})
}
output.WriteTable(cmd.OutOrStdout(), []string{"Name", "Org", "ID"}, entries)
return nil
},
}
return cmd
}

15
cmd/cluster/list.md Normal file
View File

@@ -0,0 +1,15 @@
==Long==
# List
List all clusters from IdentityNow
## API References:
- https://developer.sailpoint.com/idn/api/beta/get-managed-clusters
====
==Example==
```bash
sail cluster list
```
====

View File

@@ -2,47 +2,42 @@ package logConfig
import ( import (
"context" "context"
_ "embed"
"github.com/sailpoint-oss/golang-sdk/beta"
"github.com/sailpoint-oss/sailpoint-cli/internal/config" "github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/sdk" "github.com/sailpoint-oss/sailpoint-cli/internal/sdk"
"github.com/sailpoint-oss/sailpoint-cli/internal/util" "github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newGetCmd() *cobra.Command { //go:embed get.md
var getHelp string
func newGetCommand() *cobra.Command {
help := util.ParseHelp(getHelp)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "get", Use: "get",
Short: "Return a Virtual Appliances log configuration", Short: "Get a Virtual Appliances clusters log configuration",
Long: "\nReturn a Virtual Appliances log configuration\n\n", Long: help.Long,
Example: "sail va log get", Example: help.Example,
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
var output []beta.ClientLogConfiguration
apiClient, err := config.InitAPIClient() apiClient, err := config.InitAPIClient()
if err != nil { if err != nil {
return err return err
} }
for i := 0; i < len(args); i++ { for _, clusterId := range args {
clusterId := args[i]
configuration, resp, err := apiClient.Beta.ManagedClustersApi.GetClientLogConfiguration(context.TODO(), clusterId).Execute() configuration, resp, err := apiClient.Beta.ManagedClustersApi.GetClientLogConfiguration(context.TODO(), clusterId).Execute()
if err != nil { if err != nil {
return sdk.HandleSDKError(resp, err) return sdk.HandleSDKError(resp, err)
} }
if configuration != nil { cmd.Println(util.PrettyPrint(configuration))
output = append(output, *configuration)
}
} }
cmd.Println(util.PrettyPrint(output))
return nil return nil
}, },
} }

View File

@@ -0,0 +1,11 @@
==Long==
# Get
Get a managed clusters log configuration
====
==Example==
```bash
sail cluster log get 2c91808580f6cc1a01811af8cf5f18cb
```
====

View File

@@ -2,25 +2,31 @@
package logConfig package logConfig
import ( import (
"fmt" _ "embed"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func NewLogCmd() *cobra.Command { //go:embed logConfig.md
var logConfigHelp string
func NewLogCommand() *cobra.Command {
help := util.ParseHelp(logConfigHelp)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "log", Use: "log",
Short: "Interact with a SailPoint Virtual Appliances log configuration", Short: "Interact with a SailPoint Virtual Appliances log configuration",
Long: "\nInteract with SailPoint Virtual Appliances log configuration\n\n", Long: help.Long,
Example: help.Example,
Aliases: []string{"l"}, Aliases: []string{"l"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
_, _ = fmt.Fprint(cmd.OutOrStdout(), cmd.UsageString()) cmd.Help()
}, },
} }
cmd.AddCommand( cmd.AddCommand(
newGetCmd(), newGetCommand(),
newSetCmd(), newSetCommand(),
) )
return cmd return cmd

View File

@@ -0,0 +1,16 @@
==Long==
# Log Config
Get or set a managed clusters log configuration
## API Reference:
- https://developer.sailpoint.com/idn/api/beta/managed-clusters
====
==Example==
```bash
sail cluster log get 2c91808580f6cc1a01811af8cf5f18cb
sail cluster log set 2c91808580f6cc1a01811af8cf5f18cb -r TRACE -d 30 -c sailpoint.connector.ADLDAPConnector=TRACE
```
====

View File

@@ -2,6 +2,8 @@ package logConfig
import ( import (
"context" "context"
_ "embed"
"errors"
"strings" "strings"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
@@ -12,7 +14,11 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newSetCmd() *cobra.Command { //go:embed set.md
var setHelp string
func newSetCommand() *cobra.Command {
help := util.ParseHelp(setHelp)
var level string var level string
var durationInMinutes int32 var durationInMinutes int32
var connectors []string var connectors []string
@@ -20,31 +26,30 @@ func newSetCmd() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "set", Use: "set",
Short: "Set a Virtual Appliances log configuration", Short: "Set a Virtual Appliances log configuration",
Long: "\nSet a Virtual Appliances log configuration\n\nA list of Connectors can be found here:\nhttps://community.sailpoint.com/t5/IdentityNow-Articles/Enabling-Connector-Logging-in-IdentityNow/ta-p/188107\n\n", Long: help.Long,
Example: "sail va log set", Example: help.Example,
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
rootLevel := beta.StandardLevel(level) rootLevel := beta.StandardLevel(level)
if rootLevel.IsValid() == false { if !rootLevel.IsValid() {
log.Fatal("logLevel provided is invalid", "level", level) return errors.New("invalid logLevel: " + level)
} }
if durationInMinutes < 5 || durationInMinutes > 1440 { if durationInMinutes < 5 || durationInMinutes > 1440 {
log.Fatal("durationInMinutes provided is invalid", "durationInMinutes", durationInMinutes) return errors.New("invalid durationInMinutes: " + string(durationInMinutes))
} }
var output []beta.ClientLogConfiguration
apiClient, err := config.InitAPIClient() apiClient, err := config.InitAPIClient()
if err != nil { if err != nil {
return err return err
} }
logLevels := make(map[string]beta.StandardLevel) logLevels := make(map[string]beta.StandardLevel)
for j := 0; j < len(connectors); j++ {
connector := connectors[j] for _, connector := range connectors {
parts := strings.Split(connector, "=") parts := strings.Split(connector, "=")
conLevel := beta.StandardLevel(parts[1]) conLevel := beta.StandardLevel(parts[1])
if conLevel.IsValid() { if conLevel.IsValid() {
@@ -54,23 +59,16 @@ func newSetCmd() *cobra.Command {
} }
} }
logConfig := beta.NewClientLogConfiguration(durationInMinutes, rootLevel) for _, clusterId := range args {
logConfig.LogLevels = &logLevels
for i := 0; i < len(args); i++ { configuration, resp, err := apiClient.Beta.ManagedClustersApi.PutClientLogConfiguration(context.TODO(), clusterId).ClientLogConfiguration(beta.ClientLogConfiguration{DurationMinutes: durationInMinutes, RootLevel: rootLevel, LogLevels: &logLevels}).Execute()
clusterId := args[i]
configuration, resp, err := apiClient.Beta.ManagedClustersApi.PutClientLogConfiguration(context.TODO(), clusterId).ClientLogConfiguration(*logConfig).Execute()
if err != nil { if err != nil {
return sdk.HandleSDKError(resp, err) return sdk.HandleSDKError(resp, err)
} }
output = append(output, *configuration) cmd.Println(util.PrettyPrint(configuration))
} }
cmd.Println(util.PrettyPrint(output))
return nil return nil
}, },
} }

View File

@@ -0,0 +1,13 @@
==Long==
# Set
Set a managed clusters log configuration
A list of Connectors (can be found here)[https://community.sailpoint.com/t5/IdentityNow-Articles/Enabling-Connector-Logging-in-IdentityNow/ta-p/188107]
====
==Example==
```bash
sail cluster log set 2c91808580f6cc1a01811af8cf5f18cb -r TRACE -d 30 -c sailpoint.connector.ADLDAPConnector=TRACE
```
====

View File

@@ -41,7 +41,7 @@ func NewConnCmd(term terminal.Terminal) *cobra.Command {
conn.PersistentFlags().StringP("conn-endpoint", "e", connectorsEndpoint, "Override connectors endpoint") conn.PersistentFlags().StringP("conn-endpoint", "e", connectorsEndpoint, "Override connectors endpoint")
conn.AddCommand( conn.AddCommand(
newConnInitCmd(), newConnInitCommand(),
newConnListCmd(Client), newConnListCmd(Client),
newConnGetCmd(Client), newConnGetCmd(Client),
newConnUpdateCmd(Client), newConnUpdateCmd(Client),

View File

@@ -29,7 +29,7 @@ const (
// newConnInitCmd is a connectors subcommand used to initialize a new connector project. // newConnInitCmd is a connectors subcommand used to initialize a new connector project.
// It accepts one argument, project name, and generates appropriate directories and files // It accepts one argument, project name, and generates appropriate directories and files
// to set up a working, sample project. // to set up a working, sample project.
func newConnInitCmd() *cobra.Command { func newConnInitCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "init <connector-name>", Use: "init <connector-name>",
Short: "Initialize new connector project", Short: "Initialize new connector project",

View File

@@ -11,7 +11,7 @@ import (
) )
func TestNewConnInitCmd_noArgs(t *testing.T) { func TestNewConnInitCmd_noArgs(t *testing.T) {
cmd := newConnInitCmd() cmd := newConnInitCommand()
cmd.SetArgs([]string{}) cmd.SetArgs([]string{})
if err := cmd.Execute(); err == nil { if err := cmd.Execute(); err == nil {
@@ -20,7 +20,7 @@ func TestNewConnInitCmd_noArgs(t *testing.T) {
} }
func TestNewConnInitCmd_emptyName(t *testing.T) { func TestNewConnInitCmd_emptyName(t *testing.T) {
cmd := newConnInitCmd() cmd := newConnInitCommand()
b := new(bytes.Buffer) b := new(bytes.Buffer)
cmd.SetErr(b) cmd.SetErr(b)
@@ -42,7 +42,7 @@ func TestNewConnInitCmd_emptyName(t *testing.T) {
} }
func TestNewConnInitCmd(t *testing.T) { func TestNewConnInitCmd(t *testing.T) {
cmd := newConnInitCmd() cmd := newConnInitCommand()
testProjName := "test-connector-project" testProjName := "test-connector-project"

View File

@@ -1,17 +1,22 @@
package environment package environment
import ( import (
_ "embed"
"fmt"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/sailpoint-oss/sailpoint-cli/internal/config" "github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/terminal" "github.com/sailpoint-oss/sailpoint-cli/internal/terminal"
"github.com/sailpoint-oss/sailpoint-cli/internal/tui"
"github.com/sailpoint-oss/sailpoint-cli/internal/util" "github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"golang.org/x/exp/maps"
) )
//go:embed environment.md
var environmentHelp string
func NewEnvironmentCommand() *cobra.Command { func NewEnvironmentCommand() *cobra.Command {
help := util.ParseHelp(environmentHelp)
var env string var env string
var overwrite bool var overwrite bool
var erase bool var erase bool
@@ -19,27 +24,18 @@ func NewEnvironmentCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "environment", Use: "environment",
Short: "Manage Environments for the CLI", Short: "Manage Environments for the CLI",
Long: "\nManage Environments for the CLI\n\n", Long: help.Long,
Example: "sail env dev", Example: help.Example,
Aliases: []string{"env"}, Aliases: []string{"env"},
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
environments := config.GetEnvironments() environments := config.GetEnvironments()
envKeys := maps.Keys(environments)
if len(args) > 0 { if len(args) > 0 {
env = args[0] env = args[0]
} else { } else {
var choices []tui.Choice env = config.GetActiveEnvironment()
for i := 0; i < len(envKeys); i++ {
choices = append(choices, tui.Choice{Title: envKeys[i]})
}
selectedEnv, err := tui.PromptList(choices, "Please select an existing environment: ")
if err != nil {
return err
}
env = selectedEnv.Title
} }
if env != "" { if env != "" {
@@ -49,8 +45,9 @@ func NewEnvironmentCommand() *cobra.Command {
if show { if show {
log.Warn("You are about to Print out the Environment", "env", env) log.Warn("You are about to Print out the Environment", "env", env)
res := terminal.InputPrompt("Press Enter to continue") res := terminal.InputPrompt("Press Enter to continue")
log.Info("Response", "res", res)
if res == "" { if res == "" {
util.PrettyPrint(foundEnv) fmt.Println(util.PrettyPrint(foundEnv))
} }
} else if erase { } else if erase {
log.Warn("You are about to Erase the Environment", "env", env) log.Warn("You are about to Erase the Environment", "env", env)
@@ -73,7 +70,7 @@ func NewEnvironmentCommand() *cobra.Command {
} }
} else { } else {
log.Warn("No Environment Provided") cmd.Help()
} }
return nil return nil

View File

@@ -0,0 +1,48 @@
==Long==
# Environment
Configure SailPoint IdentityNow environments for the CLI
====
==Example==
## Adding an new environment
You can add new environments by supplying a name that does not already exist. The CLI will prompt you for the Tenant URL and API URL.
```bash
sail environment {environment-name}
```
## Switching environments
You can switch between environments by supplying the name of an existing environment you want to switch to.
```bash
sail environment {environment-name}
```
## Removing an environment
You can remove an environment by supplying the name of an existing environment you want to remove in combination with the `--erase` flag.
```bash
sail environment {environment-name} --erase
```
## Overwriting an environment
You can overwrite an environment by supplying the name of an existing environment you want to overwrite in combination with the `--overwrite` flag.
```bash
sail environment {environment-name} --overwrite
```
## Showing an environment
You can print an environment by supplying the name of an existing environment you want to print in combination with the `--show` flag.
```bash
sail environment {environment-name} --show
```
====

View File

@@ -2,27 +2,126 @@
package report package report
import ( import (
"context"
_ "embed"
"encoding/json"
"fmt" "fmt"
"os"
"path"
"strings"
"github.com/charmbracelet/log"
v3 "github.com/sailpoint-oss/golang-sdk/v3"
"github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/output"
"github.com/sailpoint-oss/sailpoint-cli/internal/templates"
"github.com/sailpoint-oss/sailpoint-cli/internal/terminal"
"github.com/sailpoint-oss/sailpoint-cli/internal/types"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
//go:embed report.md
var reportHelp string
func NewReportCommand() *cobra.Command { func NewReportCommand() *cobra.Command {
help := util.ParseHelp(reportHelp)
var save bool
var folderPath string
var template string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "report", Use: "report",
Short: "Generate a report from a template using IdentityNow search queries", Short: "Generate a report from a template using IdentityNow search queries",
Long: "Generate a report from a template using IdentityNow search queries", Long: help.Long,
Example: "sail report \"\"", Example: help.Example,
Aliases: []string{"rep"}, Aliases: []string{"rep"},
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) { RunE: func(cmd *cobra.Command, args []string) error {
_, _ = fmt.Fprint(cmd.OutOrStdout(), cmd.UsageString())
err := config.InitConfig()
if err != nil {
return err
}
apiClient, err := config.InitAPIClient()
if err != nil {
return err
}
var selectedTemplate templates.ReportTemplate
reportTemplates, err := templates.GetReportTemplates()
if err != nil {
return err
}
if len(args) > 0 {
template = args[0]
} else {
template, err = templates.SelectTemplate(reportTemplates)
if err != nil {
return err
}
}
if template == "" {
return fmt.Errorf("no template specified")
}
log.Info("Selected Template", "template", template)
matches := types.Filter(reportTemplates, func(st templates.ReportTemplate) bool { return st.Name == template })
if len(matches) < 1 {
return fmt.Errorf("no template matches for %s", template)
} else if len(matches) > 1 {
log.Warn("multiple template matches, the first match will be used", "template", template)
}
selectedTemplate = matches[0]
if len(selectedTemplate.Variables) > 0 {
for _, varEntry := range selectedTemplate.Variables {
resp := terminal.InputPrompt("Input " + varEntry.Prompt + ":")
selectedTemplate.Raw = []byte(strings.ReplaceAll(string(selectedTemplate.Raw), "{{"+varEntry.Name+"}}", resp))
}
err := json.Unmarshal(selectedTemplate.Raw, &selectedTemplate.Queries)
if err != nil {
return err
}
}
for i, currentQuery := range selectedTemplate.Queries {
searchQuery := v3.NewSearch()
query := v3.NewQuery()
query.SetQuery(currentQuery.QueryString)
searchQuery.Query = query
resp, err := apiClient.V3.SearchApi.SearchCount(context.TODO()).Search(*searchQuery).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", resp)
}
selectedTemplate.Queries[i].ResultCount = resp.Header["X-Total-Count"][0]
}
if save {
fileName := selectedTemplate.Name + ".json"
err := output.SaveJSONFile(selectedTemplate.Queries, fileName, folderPath)
if err != nil {
return err
}
log.Info("Report saved", "path", path.Join(folderPath, fileName))
} else {
cmd.Println(util.PrettyPrint(selectedTemplate.Queries))
}
return nil
}, },
} }
cmd.AddCommand( cmd.Flags().BoolVarP(&save, "save", "s", false, "save the report to a file")
newTemplateCmd(), cmd.Flags().StringVarP(&folderPath, "folderPath", "f", "reports", "folder path to save the reports in. If the directory doesn't exist, then it will be automatically created. (default is the current working directory)")
)
return cmd return cmd

21
cmd/report/report.md Normal file
View File

@@ -0,0 +1,21 @@
==Long==
# Report
Generate a report from IdentityNow
## API References:
- https://developer.sailpoint.com/idn/api/v3/search
====
==Example==
## Selecting a report
```bash
sail report
```
## Running a specific report
```bash
sail report {report-name}
```
====

View File

@@ -1,113 +0,0 @@
// Copyright (c) 2021, SailPoint Technologies, Inc. All rights reserved.
package report
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/charmbracelet/log"
v3 "github.com/sailpoint-oss/golang-sdk/v3"
"github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/templates"
"github.com/sailpoint-oss/sailpoint-cli/internal/terminal"
"github.com/sailpoint-oss/sailpoint-cli/internal/types"
"github.com/spf13/cobra"
)
func newTemplateCmd() *cobra.Command {
var outputTypes []string
var folderPath string
var template string
cmd := &cobra.Command{
Use: "template",
Short: "generate a report using a template",
Long: "generate a report from IdentityNow using a template",
Example: "sail report template",
Aliases: []string{"temp"},
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
err := config.InitConfig()
if err != nil {
return err
}
apiClient, err := config.InitAPIClient()
if err != nil {
return err
}
var selectedTemplate templates.ReportTemplate
reportTemplates, err := templates.GetReportTemplates()
if err != nil {
return err
}
if len(args) > 0 {
template = args[0]
} else {
template, err = templates.SelectTemplate(reportTemplates)
if err != nil {
return err
}
}
if template == "" {
return fmt.Errorf("no template specified")
}
log.Info("Selected Template", "template", template)
matches := types.Filter(reportTemplates, func(st templates.ReportTemplate) bool { return st.Name == template })
if len(matches) < 1 {
return fmt.Errorf("no template matches for %s", template)
} else if len(matches) > 1 {
log.Warn("multiple template matches, the first match will be used", "template", template)
}
selectedTemplate = matches[0]
varCount := len(selectedTemplate.Variables)
if varCount > 0 {
for i := 0; i < varCount; i++ {
varEntry := selectedTemplate.Variables[i]
resp := terminal.InputPrompt("Input " + varEntry.Prompt + ":")
selectedTemplate.Raw = []byte(strings.ReplaceAll(string(selectedTemplate.Raw), "{{"+varEntry.Name+"}}", resp))
}
err := json.Unmarshal(selectedTemplate.Raw, &selectedTemplate.Queries)
if err != nil {
return err
}
}
for i := 0; i < len(selectedTemplate.Queries); i++ {
currentQuery := selectedTemplate.Queries[i]
searchQuery := v3.NewSearch()
query := v3.NewQuery()
query.SetQuery(currentQuery.QueryString)
searchQuery.Query = query
resp, err := apiClient.V3.SearchApi.SearchCount(context.TODO()).Search(*searchQuery).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", resp)
}
selectedTemplate.Queries[i].ResultCount = resp.Header["X-Total-Count"][0]
}
for i := 0; i < len(selectedTemplate.Queries); i++ {
currentQuery := selectedTemplate.Queries[i]
fmt.Println(currentQuery.QueryTitle + ": " + currentQuery.ResultCount)
}
return nil
},
}
cmd.Flags().StringArrayVarP(&outputTypes, "output", "o", []string{"json"}, "the sort value for the api call (examples)")
cmd.Flags().StringVarP(&folderPath, "folderPath", "f", "reports", "folder path to save the reports in. If the directory doesn't exist, then it will be automatically created. (default is the current working directory)")
return cmd
}

View File

@@ -1,28 +1,38 @@
// Copyright (c) 2021, SailPoint Technologies, Inc. All rights reserved.
package root package root
import ( import (
"fmt" _ "embed"
"github.com/sailpoint-oss/sailpoint-cli/cmd/configure" "github.com/sailpoint-oss/sailpoint-cli/cmd/cluster"
"github.com/sailpoint-oss/sailpoint-cli/cmd/connector" "github.com/sailpoint-oss/sailpoint-cli/cmd/connector"
"github.com/sailpoint-oss/sailpoint-cli/cmd/environment" "github.com/sailpoint-oss/sailpoint-cli/cmd/environment"
"github.com/sailpoint-oss/sailpoint-cli/cmd/report" "github.com/sailpoint-oss/sailpoint-cli/cmd/report"
"github.com/sailpoint-oss/sailpoint-cli/cmd/sdk"
"github.com/sailpoint-oss/sailpoint-cli/cmd/search" "github.com/sailpoint-oss/sailpoint-cli/cmd/search"
"github.com/sailpoint-oss/sailpoint-cli/cmd/set" "github.com/sailpoint-oss/sailpoint-cli/cmd/set"
"github.com/sailpoint-oss/sailpoint-cli/cmd/spconfig" "github.com/sailpoint-oss/sailpoint-cli/cmd/spconfig"
"github.com/sailpoint-oss/sailpoint-cli/cmd/transform" "github.com/sailpoint-oss/sailpoint-cli/cmd/transform"
"github.com/sailpoint-oss/sailpoint-cli/cmd/va" "github.com/sailpoint-oss/sailpoint-cli/cmd/va"
"github.com/sailpoint-oss/sailpoint-cli/cmd/workflow"
"github.com/sailpoint-oss/sailpoint-cli/internal/terminal" "github.com/sailpoint-oss/sailpoint-cli/internal/terminal"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
) )
var version = "1.2.0" var version = "1.2.0"
func NewRootCmd() *cobra.Command { //go:embed root.md
var rootHelp string
func NewRootCommand() *cobra.Command {
help := util.ParseHelp(rootHelp)
var env string
var debug bool
root := &cobra.Command{ root := &cobra.Command{
Use: "sail", Use: "sail",
Short: "The SailPoint CLI allows you to administer your IdentityNow tenant from the command line.\nNavigate to https://developer.sailpoint.com/idn/tools/cli to learn more.", Long: help.Long,
Example: help.Example,
Version: version, Version: version,
SilenceUsage: true, SilenceUsage: true,
CompletionOptions: cobra.CompletionOptions{ CompletionOptions: cobra.CompletionOptions{
@@ -31,22 +41,30 @@ func NewRootCmd() *cobra.Command {
DisableDescriptions: true, DisableDescriptions: true,
}, },
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
_, _ = fmt.Fprint(cmd.OutOrStdout(), cmd.UsageString()) cmd.Help()
}, },
} }
t := &terminal.Term{} t := &terminal.Term{}
root.AddCommand( root.AddCommand(
set.NewSetCommand(), cluster.NewClusterCommand(),
environment.NewEnvironmentCommand(),
configure.NewConfigureCmd(t),
connector.NewConnCmd(t), connector.NewConnCmd(t),
transform.NewTransformCmd(), environment.NewEnvironmentCommand(),
va.NewVACmd(t),
search.NewSearchCmd(),
spconfig.NewSPConfigCmd(),
report.NewReportCommand(), report.NewReportCommand(),
sdk.NewSDKCommand(),
search.NewSearchCommand(),
set.NewSetCmd(t),
spconfig.NewSPConfigCommand(),
transform.NewTransformCommand(),
va.NewVACommand(t),
workflow.NewWorkflowCommand(),
) )
root.PersistentFlags().StringVarP(&env, "env", "", "", "Environment to use for SailPoint CLI commands")
root.PersistentFlags().BoolVarP(&debug, "debug", "", false, "Enable debug logging")
viper.BindPFlag("activeenvironment", root.PersistentFlags().Lookup("env"))
viper.BindPFlag("debug", root.PersistentFlags().Lookup("debug"))
return root return root
} }

13
cmd/root/root.md Normal file
View File

@@ -0,0 +1,13 @@
==Long==
# SailPoint CLI
The SailPoint CLI allows you to administer your IdentityNow tenant from the command line.
Navigate to the [CLI Documentation](https://developer.sailpoint.com/idn/tools/cli) for more information.
====
==Example==
```bash
sail
```
====

View File

@@ -5,6 +5,7 @@ package root
import ( import (
"bytes" "bytes"
"io" "io"
"strings"
"testing" "testing"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
@@ -12,16 +13,16 @@ import (
// Expected number of subcommands to `sail` root command // Expected number of subcommands to `sail` root command
const ( const (
numRootSubcommands = 9 numRootSubcommands = 11
) )
func TestNewRootCmd_noArgs(t *testing.T) { func TestNewRootCmd_noArgs(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
cmd := NewRootCmd() cmd := NewRootCommand()
if len(cmd.Commands()) != numRootSubcommands { if len(cmd.Commands()) != numRootSubcommands {
t.Fatalf("expected: %d, actual: %d", len(cmd.Commands()), numRootSubcommands) t.Fatalf("expected: %d, actual: %d", numRootSubcommands, len(cmd.Commands()))
} }
b := new(bytes.Buffer) b := new(bytes.Buffer)
@@ -38,7 +39,7 @@ func TestNewRootCmd_noArgs(t *testing.T) {
t.Fatalf("error read out: %v", err) t.Fatalf("error read out: %v", err)
} }
if string(out) != cmd.UsageString() { if !strings.Contains(string(out), cmd.UsageString()) {
t.Errorf("expected: %s, actual: %s", cmd.UsageString(), string(out)) t.Errorf("expected: %s, actual: %s", cmd.UsageString(), string(out))
} }
} }
@@ -47,7 +48,7 @@ func TestNewRootCmd_completionDisabled(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
cmd := NewRootCmd() cmd := NewRootCommand()
b := new(bytes.Buffer) b := new(bytes.Buffer)
cmd.SetOut(b) cmd.SetOut(b)

94
cmd/sdk/config.go Normal file
View File

@@ -0,0 +1,94 @@
// Copyright (c) 2023, SailPoint Technologies, Inc. All rights reserved.
package sdk
import (
"encoding/json"
"fmt"
"os"
"path"
"github.com/charmbracelet/log"
"github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/spf13/cobra"
)
type Config struct {
ClientID string
ClientSecret string
BaseUrl string
}
func (c Config) printEnv() {
fmt.Println("BASE_URL=" + c.BaseUrl)
fmt.Println("CLIENT_ID=" + c.ClientID)
fmt.Println("CLIENT_SECRET=" + c.ClientSecret)
}
func newConfigCommand() *cobra.Command {
var env bool
cmd := &cobra.Command{
Use: "config",
Short: "Initialize a configuration json file for an SDK project",
Long: "\nInitialize a configuration json file for an SDK project\n\nRunning with no arguments will use the currently active environment\n",
Example: "sail sdk init config\nsail sdk init config <environment name>",
Aliases: []string{"conf"},
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var envName string
if len(args) > 0 {
envName = args[0]
} else {
envName = config.GetActiveEnvironment()
}
clientID, err := config.GetClientID(envName)
if err != nil {
return err
}
clientSecret, err := config.GetClientSecret(envName)
if err != nil {
return err
}
SDKConfig := Config{ClientID: clientID, ClientSecret: clientSecret, BaseUrl: config.GetEnvBaseUrl(envName)}
if env {
SDKConfig.printEnv()
} else {
workingDir, err := os.Getwd()
if err != nil {
return err
}
configPath := path.Join(workingDir, "config.json")
file, err := os.Create(configPath)
if err != nil {
return err
}
defer file.Close()
configJson, err := json.MarshalIndent(SDKConfig, "", " ")
if err != nil {
return err
}
_, err = file.Write(configJson)
if err != nil {
return err
}
log.Info("config file created", "path", configPath)
}
return nil
},
}
cmd.Flags().BoolVarP(&env, "environment", "e", false, "Print out the config values in .env format to the terminal rather than to a config file")
return cmd
}

44
cmd/sdk/go.go Normal file
View File

@@ -0,0 +1,44 @@
// Copyright (c) 2023, SailPoint Technologies, Inc. All rights reserved.
package sdk
import (
"embed"
"github.com/sailpoint-oss/sailpoint-cli/internal/initialize"
"github.com/spf13/cobra"
)
//go:embed golang/*
var goTemplateContents embed.FS
const goTemplateDirName = "golang"
func newGolangCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "golang",
Short: "Initialize a new GO SDK project",
Long: "\nInitialize a new GO SDK project\n\n",
Example: "sail sdk init golang\nsail sdk init go example-project",
Aliases: []string{"go"},
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var projName string
var err error
if len(args) > 0 {
projName = args[0]
} else {
projName = "go-template"
}
err = initialize.InitializeProject(goTemplateContents, goTemplateDirName, projName)
if err != nil {
return err
}
return nil
},
}
return cmd
}

View File

@@ -0,0 +1,30 @@
module sdk
go 1.18
require github.com/sailpoint-oss/golang-sdk v1.0.1
require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.15.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
golang.org/x/net v0.6.0 // indirect
golang.org/x/oauth2 v0.5.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

501
cmd/sdk/golang/go.sum Normal file
View File

@@ -0,0 +1,501 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/sailpoint-oss/golang-sdk v1.0.1 h1:JFqoW5K7/RkwzqhEnh0bJfcDc5CDAwrf9TxXnYqK/Ho=
github.com/sailpoint-oss/golang-sdk v1.0.1/go.mod h1:aMxZu5ieAI89duSsOy/R7+dXwk5EbF6CAEvXt8ivMYw=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
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=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
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=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

94
cmd/sdk/golang/sdk.go Normal file
View File

@@ -0,0 +1,94 @@
package main
import (
"context"
"encoding/json"
"fmt"
"os"
sailpoint "github.com/sailpoint-oss/golang-sdk"
v3 "github.com/sailpoint-oss/golang-sdk/v3"
)
func main() {
ctx := context.TODO()
configuration := sailpoint.NewDefaultConfiguration()
apiClient := sailpoint.NewAPIClient(configuration)
configuration.HTTPClient.RetryMax = 10
getBeta(ctx, apiClient)
//getAllPaginatedResults(ctx, apiClient)
}
func getResults(ctx context.Context, apiClient *sailpoint.APIClient) {
resp, r, err := apiClient.V3.AccountsApi.ListAccounts(ctx).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `AccountsApi.ListAccount``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}
// response from `ListAccounts`: []Account
fmt.Fprintf(os.Stdout, "First response from `AccountsApi.ListAccount`: %v\n", resp[0].Name)
}
func getSearchResults(ctx context.Context, apiClient *sailpoint.APIClient) {
search := v3.NewSearchWithDefaults()
search.Indices = append(search.Indices, "identities")
searchString := []byte(`
{
"indices": [
"identities"
],
"query": {
"query": "*"
},
"sort": [
"-name"
]
}
`)
search.UnmarshalJSON(searchString)
resp, r, err := sailpoint.PaginateSearchApi(ctx, apiClient, *search, 0, 10, 10000)
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `AccountsApi.ListAccount``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}
// response from `search`
for i := 0; i < len(resp); i++ {
fmt.Println(resp[i]["name"])
}
}
func getTransformResults(ctx context.Context, apiClient *sailpoint.APIClient) {
resp, r, err := apiClient.V3.TransformsApi.ListTransforms(ctx).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `TransformsApi.GetTransformsList``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}
b, _ := json.Marshal(resp[0].Attributes)
fmt.Fprintf(os.Stdout, "First response from `TransformsApi.GetTransformsList`: %v\n", string(b))
}
func getAllPaginatedResults(ctx context.Context, apiClient *sailpoint.APIClient) {
resp, r, err := sailpoint.PaginateWithDefaults[v3.Account](apiClient.V3.AccountsApi.ListAccounts(ctx))
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `AccountsApi.ListAccount``: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}
// response from `ListAccounts`: []Account
fmt.Fprintf(os.Stdout, "First response from `AccountsApi.ListAccount`: %v\n", resp[0].Name)
}
func getBeta(ctx context.Context, apiClient *sailpoint.APIClient) {
resp, _, err := apiClient.Beta.AccountsApi.ListAccounts(context.TODO()).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `AccountsApi.ListAccount``: %v\n", err)
//fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}
// response from `ListAccounts`: []Account
fmt.Fprintf(os.Stdout, "First response from `AccountsApi.ListAccount`: %v\n", resp)
}

30
cmd/sdk/init.go Normal file
View File

@@ -0,0 +1,30 @@
// Copyright (c) 2023, SailPoint Technologies, Inc. All rights reserved.
package sdk
import (
"github.com/spf13/cobra"
)
func newInitCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize SDK projects",
Long: "\nInitialize SDK projects\n\n",
Example: "sail sdk init",
Aliases: []string{"temp"},
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
cmd.AddCommand(
newTypescriptCommand(),
newGolangCommand(),
newPowerShellCommand(),
newConfigCommand(),
)
return cmd
}

View File

@@ -0,0 +1,28 @@
$JSON = @"
{
"indices": [
"identities"
],
"query": {
"query": "*",
"fields": [
"name"
]
},
"sort": [
"-displayName"
]
}
"@
$Search = ConvertFrom-JsonToSearch -Json $JSON
try {
Invoke-PaginateSearch -Increment 50 -Limit 10000 -Search $Search
} catch {
Write-Host ("Exception occurred when calling Invoke-PaginateSearch: {0}" -f $_.ErrorDetails)
Write-Host ("Response headers: {0}" -f $_.Exception.Response.Headers)
}

View File

@@ -0,0 +1,14 @@
$Parameters = @{
"Filters" = 'name co "Andrew"'
}
# Accounts List
try {
Invoke-Paginate -Function "Get-Accounts" -Increment 250 -Limit 1000 -InitialOffset 0 -Parameters $Parameters
} catch {
Write-Host ("Exception occurred when calling Invoke-Paginate: {0}" -f $_.ErrorDetails)
Write-Host ("Response headers: {0}" -f $_.Exception.Response.Headers)
}

View File

@@ -0,0 +1,14 @@
$ENT = @(
@{
op = "replace"
path = "/privileged"
value = $false
}
)
try {
Update-BetaEntitlement -Id "2c9180848366cdc701837b78f5ce58be" -JsonPatchOperation $ENT
} catch {
Write-Host ("Exception occurred when calling Update-BetaEntitlement: {0}" -f $_.ErrorDetails)
Write-Host ("Response headers: {0}" -f $_.Exception.Response.Headers)
}

View File

@@ -0,0 +1,14 @@
$Limit = 250 # Int32 | Max number of results to return. See [V3 API Standard Collection Parameters](https://developer.sailpoint.com/idn/api/standard-collection-parameters) for more information. (optional) (default to 250)
$Offset = 0 # Int32 | Offset into the full result set. Usually specified with *limit* to paginate through the results. See [V3 API Standard Collection Parameters](https://developer.sailpoint.com/idn/api/standard-collection-parameters) for more information. (optional) (default to 0)
$Count = $true # Boolean | If *true* it will populate the *X-Total-Count* response header with the number of results that would be returned if *limit* and *offset* were ignored. Since requesting a total count can have a performance impact, it is recommended not to send **count=true** if that value will not be used. See [V3 API Standard Collection Parameters](https://developer.sailpoint.com/idn/api/standard-collection-parameters) for more information. (optional) (default to $false)
$Filters = 'sourceId eq "f4e73766efdf4dc6acdeed179606d694"' # String | Filter results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/idn/api/standard-collection-parameters#filtering-results) Filtering is supported for the following fields and operators: **id**: *eq, in* **identityId**: *eq* **name**: *eq, in* **nativeIdentity**: *eq, in* **sourceId**: *eq, in* **uncorrelated**: *eq* (optional)
# Accounts List
try {
Get-Accounts -Limit $Limit -Offset $Offset -Count $Count -Filters $Filters
} catch {
Write-Host ("Exception occurred when calling Get-Accounts: {0}" -f $_.ErrorDetails)
Write-Host ("Response headers: {0}" -f $_.Exception.Response.Headers)
}

View File

@@ -0,0 +1,22 @@
$Json = @"
{
"indices": [
"identities"
],
"query": {
"query": "*",
"fields": [
"name"
]
}
}
"@
$Search = ConvertFrom-JsonToSearch -Json $Json
try {
Search-Post -Search $Search
} catch {
Write-Host ("Exception occurred when calling Search-Post: {0}" -f $_.ErrorDetails)
Write-Host ("Response headers: {0}" -f $_.Exception.Response.Headers)
}

View File

@@ -0,0 +1,24 @@
# Create transform
$JSON = @"
{
"name": "New Transform",
"type": "lookup",
"attributes" : {
"table" : {
"USA": "Americas",
"FRA": "EMEA",
"AUS": "APAC",
"default": "Unknown Region"
}
}
}
"@
$Transform = ConvertFrom-JsonToTransform -Json $JSON
try {
New-Transform -Transform $Transform
} catch {
Write-Host ("Exception occurred when calling New-Transform: {0}" -f $_.ErrorDetails)
Write-Host ("Response headers: {0}" -f $_.Exception.Response.Headers)
}

44
cmd/sdk/pwsh.go Normal file
View File

@@ -0,0 +1,44 @@
// Copyright (c) 2023, SailPoint Technologies, Inc. All rights reserved.
package sdk
import (
"embed"
"github.com/sailpoint-oss/sailpoint-cli/internal/initialize"
"github.com/spf13/cobra"
)
//go:embed powershell/*
var pwshTemplateContents embed.FS
const pwshTemplateDirName = "powershell"
func newPowerShellCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "powershell",
Short: "Initialize a new PowerShell SDK project",
Long: "\nInitialize a new PowerShell SDK project\n\n",
Example: "sail sdk init powershell\nsail sdk init pwsh example-project",
Aliases: []string{"pwsh"},
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var projName string
var err error
if len(args) > 0 {
projName = args[0]
} else {
projName = "powershell-template"
}
err = initialize.InitializeProject(pwshTemplateContents, pwshTemplateDirName, projName)
if err != nil {
return err
}
return nil
},
}
return cmd
}

26
cmd/sdk/sdk.go Normal file
View File

@@ -0,0 +1,26 @@
// Copyright (c) 2023, SailPoint Technologies, Inc. All rights reserved.
package sdk
import (
"github.com/spf13/cobra"
)
func NewSDKCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "sdk",
Short: "Initialize or configure SDK projects",
Long: "\nInitialize or configure SDK projects\n\n",
Example: "sail sdk",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
cmd.AddCommand(
newInitCommand(),
)
return cmd
}

44
cmd/sdk/ts.go Normal file
View File

@@ -0,0 +1,44 @@
// Copyright (c) 2023, SailPoint Technologies, Inc. All rights reserved.
package sdk
import (
"embed"
"github.com/sailpoint-oss/sailpoint-cli/internal/initialize"
"github.com/spf13/cobra"
)
//go:embed typescript/*
var tsTemplateContents embed.FS
const tsTemplateDirName = "typescript"
func newTypescriptCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "typescript",
Short: "Initialize a new typescript SDK project",
Long: "\nInitialize a new typescript SDK project\n\n",
Example: "sail sdk init typescript\nsail sdk init ts example-project",
Aliases: []string{"ts"},
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var projName string
var err error
if len(args) > 0 {
projName = args[0]
} else {
projName = "typescript-template"
}
err = initialize.InitializeProject(tsTemplateContents, tsTemplateDirName, projName)
if err != nil {
return err
}
return nil
},
}
return cmd
}

View File

@@ -0,0 +1,16 @@
{
"name": "sailpoint-sdk",
"version": "1.0.0",
"description": "",
"main": "build/index.js",
"types": "build/index.d.ts",
"scripts": {
"start": "ts-node src/index.ts",
"build": "tsc"
},
"devDependencies": {
"ts-node": "^10.9.1",
"typescript": "^4.7.4",
"sailpoint-api-client": "^1.0.4"
}
}

View File

@@ -0,0 +1,85 @@
import { AccountsApi, Configuration, axiosRetry, Paginator, SearchApi, TransformsApi, TransformsApiCreateTransformRequest, Search, IdentityDocument} from "sailpoint-api-client"
const createTransform = async () => {
let apiConfig = new Configuration()
let api = new TransformsApi(apiConfig)
let transform: TransformsApiCreateTransformRequest =
{
transform:
{
name: "Test Transform",
type: "dateFormat",
attributes: {
inputFormat: "MMM dd yyyy, HH:mm:ss.SSS",
outputFormat: "yyyy/dd/MM"
}
}
}
const val = await api.createTransform(transform)
console.log(val)
}
const search = async () => {
let apiConfig = new Configuration()
let api = new SearchApi(apiConfig)
let search: Search = {
indices: [
"identities"
],
query: {
query: "*"
},
sort: ["-name"]
}
const val = await Paginator.paginateSearchApi(api, search, 100, 1000)
for (const result of val.data) {
const castedResult: IdentityDocument = result
console.log(castedResult.name)
}
}
const getPaginatedAccounts = async () => {
let apiConfig = new Configuration()
apiConfig.retriesConfig = {
retries: 4,
retryDelay: axiosRetry.exponentialDelay,
onRetry(retryCount, error, requestConfig) {
console.log(`retrying due to request error, try number ${retryCount}`)
},
}
let api = new AccountsApi(apiConfig)
const val = await Paginator.paginate(api, api.listAccounts, {limit: 100}, 10)
console.log(val)
}
const getPaginatedTransforms = async () => {
let apiConfig = new Configuration()
apiConfig.retriesConfig = {
retries: 4,
retryDelay: axiosRetry.exponentialDelay,
onRetry(retryCount, error, requestConfig) {
console.log(`retrying due to request error, try number ${retryCount}`)
},
}
let api = new TransformsApi(apiConfig)
const val = await Paginator.paginate(api, api.listTransforms, {limit: 250}, 100)
console.log(val.data.length)
}
getPaginatedTransforms()

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"module": "commonjs", /* Specify what module code is generated. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true,
"outDir": "./build",
"rootDir": "src",
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

View File

@@ -7,22 +7,29 @@ import (
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/sailpoint-oss/sailpoint-cli/internal/config" "github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/search" "github.com/sailpoint-oss/sailpoint-cli/internal/search"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newQueryCmd() *cobra.Command { func newQueryCmd(folderPath string, save bool) *cobra.Command {
var folderPath string
var indices []string var indices []string
var outputTypes []string
var sort []string var sort []string
var searchQuery string var searchQuery string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "query", Use: "query",
Short: "Manually Search using a specific Query and Indicies", Short: "Manually Search using a specific Query and Indicies",
Long: "\nRun a search query in IdentityNow using a specific Query and Indicies\n\n", Long: "\nRun a search query in IdentityNow using a specific Query and Indicies\n\n",
Example: "sail search query \"(type:provisioning AND created:[now-90d TO now])\" --indicies events", Example: "sail search query \"(type:provisioning AND created:[now-90d TO now])\" --indices events",
Aliases: []string{"que"}, Aliases: []string{"que"},
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
folderPath, _ := cmd.Flags().GetString("folderPath")
if folderPath == "" {
cmd.MarkFlagRequired("save")
}
},
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := config.InitConfig() err := config.InitConfig()
@@ -50,20 +57,21 @@ func newQueryCmd() *cobra.Command {
return err return err
} }
err = search.IterateIndices(formattedResponse, searchQuery, folderPath, outputTypes) if save {
if err != nil { err = search.IterateIndices(formattedResponse, searchQuery, folderPath, []string{"json"})
return err if err != nil {
return err
}
} else {
cmd.Println(util.PrettyPrint(formattedResponse))
} }
return nil return nil
}, },
} }
cmd.Flags().StringArrayVarP(&indices, "indices", "i", []string{}, "indices to perform the search query on (accessprofiles, accountactivities, entitlements, events, identities, roles)") cmd.Flags().StringArrayVar(&indices, "indices", []string{}, "indices to perform the search query on (accessprofiles, accountactivities, entitlements, events, identities, roles)")
cmd.Flags().StringArrayVarP(&sort, "sort", "s", []string{}, "the sort value for the api call (displayName, +id...)") cmd.Flags().StringArrayVar(&sort, "sort", []string{}, "the sort value for the api call (displayName, +id...)")
cmd.Flags().StringArrayVarP(&outputTypes, "outputTypes", "o", []string{"json"}, "the output types for the results (csv, json)")
cmd.Flags().StringVarP(&folderPath, "folderPath", "f", "search_results", "folder path to save the search results in. If the directory doesn't exist, then it will be automatically created. (default is the current working directory)")
cmd.MarkFlagRequired("indices") cmd.MarkFlagRequired("indices")
return cmd return cmd

View File

@@ -2,29 +2,32 @@
package search package search
import ( import (
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func NewSearchCmd() *cobra.Command { func NewSearchCommand() *cobra.Command {
var folderPath string
var save bool
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "search", Use: "search",
Short: "Perform Search operations in IdentityNow with a specific query or a template", Short: "Perform Search operations in IdentityNow with a specific query or a template",
Long: "\nPerform Search operations in IdentityNow with a specific query or a template\n\n", Long: "\nPerform Search operations in IdentityNow with a specific query or a template\n\n",
Example: "sail search -h", Example: "sail search",
Aliases: []string{"se"}, Aliases: []string{"se"},
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
_, _ = fmt.Fprint(cmd.OutOrStdout(), cmd.UsageString()) cmd.Help()
}, },
} }
cmd.AddCommand( cmd.AddCommand(
newQueryCmd(), newQueryCmd(folderPath, save),
newTemplateCmd(), newTemplateCmd(folderPath, save),
) )
cmd.PersistentFlags().StringVarP(&folderPath, "folderPath", "f", "", "Folder path to save the search results to. If the directory doesn't exist, then it will be created. (defaults to the current working directory)")
cmd.PersistentFlags().BoolVarP(&save, "save", "s", false, "Save the search results to a file")
return cmd return cmd
} }

View File

@@ -19,7 +19,7 @@ func TestNewSearchCommand(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
cmd := NewSearchCmd() cmd := NewSearchCommand()
b := new(bytes.Buffer) b := new(bytes.Buffer)
cmd.SetOut(b) cmd.SetOut(b)

View File

@@ -12,12 +12,11 @@ import (
"github.com/sailpoint-oss/sailpoint-cli/internal/templates" "github.com/sailpoint-oss/sailpoint-cli/internal/templates"
"github.com/sailpoint-oss/sailpoint-cli/internal/terminal" "github.com/sailpoint-oss/sailpoint-cli/internal/terminal"
"github.com/sailpoint-oss/sailpoint-cli/internal/types" "github.com/sailpoint-oss/sailpoint-cli/internal/types"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newTemplateCmd() *cobra.Command { func newTemplateCmd(folderPath string, save bool) *cobra.Command {
var outputTypes []string
var folderPath string
var template string var template string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "template", Use: "template",
@@ -26,6 +25,12 @@ func newTemplateCmd() *cobra.Command {
Example: "sail search template", Example: "sail search template",
Aliases: []string{"temp"}, Aliases: []string{"temp"},
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
folderPath, _ := cmd.Flags().GetString("folderPath")
if folderPath == "" {
cmd.MarkFlagRequired("save")
}
},
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := config.InitConfig() err := config.InitConfig()
@@ -65,10 +70,10 @@ func newTemplateCmd() *cobra.Command {
log.Warn("multiple template matches, the first match will be used", "template", template) log.Warn("multiple template matches, the first match will be used", "template", template)
} }
selectedTemplate = matches[0] selectedTemplate = matches[0]
varCount := len(selectedTemplate.Variables)
if varCount > 0 { if len(selectedTemplate.Variables) > 0 {
for i := 0; i < varCount; i++ { for _, varEntry := range selectedTemplate.Variables {
varEntry := selectedTemplate.Variables[i]
resp := terminal.InputPrompt("Input " + varEntry.Prompt + ":") resp := terminal.InputPrompt("Input " + varEntry.Prompt + ":")
selectedTemplate.Raw = []byte(strings.ReplaceAll(string(selectedTemplate.Raw), "{{"+varEntry.Name+"}}", resp)) selectedTemplate.Raw = []byte(strings.ReplaceAll(string(selectedTemplate.Raw), "{{"+varEntry.Name+"}}", resp))
} }
@@ -85,17 +90,17 @@ func newTemplateCmd() *cobra.Command {
return err return err
} }
err = search.IterateIndices(formattedResponse, selectedTemplate.SearchQuery.Query.GetQuery(), folderPath, outputTypes) if save {
if err != nil { err = search.IterateIndices(formattedResponse, selectedTemplate.SearchQuery.Query.GetQuery(), folderPath, []string{"json"})
return err if err != nil {
return err
}
} else {
cmd.Println(util.PrettyPrint(formattedResponse))
} }
return nil return nil
}, },
} }
cmd.Flags().StringArrayVarP(&outputTypes, "output types", "o", []string{"json"}, "the sort value for the api call (examples)")
cmd.Flags().StringVarP(&folderPath, "folderPath", "f", "search_results", "folder path to save the search results in. If the directory doesn't exist, then it will be automatically created. (default is the current working directory)")
return cmd return cmd
} }

View File

@@ -19,7 +19,7 @@ func TestNewTemplateCommand(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
cmd := newTemplateCmd() cmd := newTemplateCmd("", false)
b := new(bytes.Buffer) b := new(bytes.Buffer)
cmd.SetOut(b) cmd.SetOut(b)

View File

@@ -55,7 +55,7 @@ func newAuthCommand() *cobra.Command {
log.Info("Authentication method set to OAuth") log.Info("Authentication method set to OAuth")
default: default:
log.Error("Invalid Selection") log.Warn("No selection made")
} }
return nil return nil

View File

@@ -11,23 +11,17 @@ import (
func newDebugCommand() *cobra.Command { func newDebugCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "debug", Use: "debug",
Short: "Set the CLI Debug Mode", Short: "Enable or Disable Debug Mode for the CLI",
Long: "\nSet the CLI Debug Mode, Primarily used for Logging and Troubleshooting\n\n", Long: "\nEnable or Disable Debug Mode for the CLI, Primarily used for troubleshooting.\n\n",
Example: "sail set debug disable | sail set debug enable | sail set debug true | sail set debug false", Example: "sail set debug disable | sail set debug enable | sail set debug true | sail set debug false",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
switch strings.ToLower(args[0]) { switch strings.ToLower(args[0]) {
case "enable": case "enable", "true":
viper.Set("debug", true) viper.Set("debug", true)
log.Info("Debug Enabled") log.Info("Debug Enabled")
case "true": case "disable", "false":
viper.Set("debug", true)
log.Info("Debug Enabled")
case "disable":
viper.Set("debug", false)
log.Info("Debug Disabled")
case "false":
viper.Set("debug", false) viper.Set("debug", false)
log.Info("Debug Disabled") log.Info("Debug Disabled")
default: default:

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2021, SailPoint Technologies, Inc. All rights reserved. // Copyright (c) 2021, SailPoint Technologies, Inc. All rights reserved.
package configure package set
import ( import (
"github.com/sailpoint-oss/sailpoint-cli/internal/config" "github.com/sailpoint-oss/sailpoint-cli/internal/config"
@@ -7,16 +7,15 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func NewConfigureCmd(term terminal.Terminal) *cobra.Command { func newPATCommand(term terminal.Terminal) *cobra.Command {
var ClientID string var ClientID string
var ClientSecret string var ClientSecret string
var err error var err error
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "configure", Use: "pat",
Short: "Configure PAT Authentication for the currently active environment", Short: "Configure PAT Authentication for the currently active environment",
Long: "\nConfigure PAT Authentication for the CLI\n\nPrerequisites:\n\nCreate a Client ID and Client Secret\nhttps://developer.sailpoint.com/idn/api/authentication#personal-access-tokens", Long: "\nConfigure PAT Authentication for the CLI\n\nPrerequisites:\n\nCreate a Client ID and Client Secret\nhttps://developer.sailpoint.com/idn/api/authentication#personal-access-tokens",
Aliases: []string{"conf"}, Args: cobra.NoArgs,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if ClientID == "" { if ClientID == "" {

View File

@@ -2,12 +2,11 @@
package set package set
import ( import (
"fmt" "github.com/sailpoint-oss/sailpoint-cli/internal/terminal"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func NewSetCommand() *cobra.Command { func NewSetCmd(term terminal.Terminal) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "set", Use: "set",
Short: "Configure settings for the SailPoint CLI", Short: "Configure settings for the SailPoint CLI",
@@ -15,7 +14,7 @@ func NewSetCommand() *cobra.Command {
Example: "sail set", Example: "sail set",
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
_, _ = fmt.Fprint(cmd.OutOrStdout(), cmd.UsageString()) cmd.Help()
}, },
} }
@@ -24,6 +23,7 @@ func NewSetCommand() *cobra.Command {
newAuthCommand(), newAuthCommand(),
newExportTemplateCommand(), newExportTemplateCommand(),
newSearchTemplateCommand(), newSearchTemplateCommand(),
newPATCommand(term),
) )
return cmd return cmd

View File

@@ -2,21 +2,28 @@
package spconfig package spconfig
import ( import (
_ "embed"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/sailpoint-oss/sailpoint-cli/internal/config" "github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/spconfig" "github.com/sailpoint-oss/sailpoint-cli/internal/spconfig"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newDownloadCmd() *cobra.Command { //go:embed download.md
var downloadHelp string
func newDownloadCommand() *cobra.Command {
help := util.ParseHelp(downloadHelp)
var importIDs []string var importIDs []string
var exportIDs []string var exportIDs []string
var folderPath string var folderPath string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "download", Use: "download {--import <importID> --export <exportID>}",
Short: "Download the results of import or export job from IdentityNow", Short: "Download the results of import or export jobs from IdentityNow",
Long: "\nDownload the results of import or export job from IdentityNow\n\n", Long: help.Long,
Example: "sail spconfig download -export <export job id> -import <import job id>", Example: help.Example,
Aliases: []string{"down"}, Aliases: []string{"down"},
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
@@ -26,38 +33,28 @@ func newDownloadCmd() *cobra.Command {
return err return err
} }
if len(importIDs) > 0 { for _, jobId := range importIDs {
for i := 0; i < len(importIDs); i++ { log.Info("Checking Import Job", "JobID", jobId)
jobId := importIDs[i] err := spconfig.DownloadImport(*apiClient, jobId, "spconfig-import-"+jobId+".json", folderPath)
log.Info("Checking Import Job", "JobID", jobId) if err != nil {
err := spconfig.DownloadImport(*apiClient, jobId, "spconfig-import-"+jobId+".json", folderPath) return err
if err != nil {
return err
}
} }
} else {
log.Info("No Import Job IDs provided")
} }
if len(exportIDs) > 0 { for _, jobId := range exportIDs {
for i := 0; i < len(exportIDs); i++ { log.Info("Checking Export Job", "JobID", jobId)
jobId := exportIDs[i] err := spconfig.DownloadExport(*apiClient, jobId, "spconfig-export-"+jobId+".json", folderPath)
log.Info("Checking Export Job", "JobID", jobId) if err != nil {
err := spconfig.DownloadExport(*apiClient, jobId, "spconfig-export-"+jobId+".json", folderPath) return err
if err != nil {
return err
}
} }
} else {
log.Info("No Export Job IDs provided")
} }
return nil return nil
}, },
} }
cmd.Flags().StringArrayVarP(&importIDs, "import", "i", []string{}, "specify the IDs of the import jobs to download results for") cmd.Flags().StringArrayVarP(&importIDs, "import", "", []string{}, "specify the IDs of the import jobs to download results for")
cmd.Flags().StringArrayVarP(&exportIDs, "export", "e", []string{}, "specify the IDs of the export jobs to download results for") cmd.Flags().StringArrayVarP(&exportIDs, "export", "", []string{}, "specify the IDs of the export jobs to download results for")
cmd.Flags().StringVarP(&folderPath, "folderPath", "f", "spconfig-exports", "folder path to save the search results in. If the directory doesn't exist, then it will be automatically created. (default is the current working directory)") cmd.Flags().StringVarP(&folderPath, "folderPath", "f", "spconfig-exports", "folder path to save the search results in. If the directory doesn't exist, then it will be automatically created. (default is the current working directory)")
return cmd return cmd

12
cmd/spconfig/download.md Normal file
View File

@@ -0,0 +1,12 @@
==Long==
# Download
Download the results of Import or Export jobs from IdentityNow
====
==Example==
```bash
sail spconfig download
```
====

View File

@@ -3,41 +3,55 @@ package spconfig
import ( import (
"context" "context"
_ "embed"
"encoding/json"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
sailpointbetasdk "github.com/sailpoint-oss/golang-sdk/beta" "github.com/sailpoint-oss/golang-sdk/beta"
"github.com/sailpoint-oss/sailpoint-cli/internal/config" "github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/spconfig" "github.com/sailpoint-oss/sailpoint-cli/internal/spconfig"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newExportCmd() *cobra.Command { //go:embed export.md
var exportHelp string
func newExportCommand() *cobra.Command {
help := util.ParseHelp(exportHelp)
var objectOptions string
var folderPath string var folderPath string
var description string var description string
var includeTypes []string var includeTypes []string
var excludeTypes []string var excludeTypes []string
var wait bool var wait bool
var payload *sailpointbetasdk.ExportPayload
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "export", Use: "export",
Short: "Start an Export job in IdentityNow", Short: "Start an Export job in IdentityNow",
Long: "\nStart an Export job in IdentityNow\n\n", Long: help.Long,
Example: "sail spconfig export", Example: help.Example,
Aliases: []string{"exp"}, Aliases: []string{"exp"},
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
var options *map[string]beta.ObjectExportImportOptions
apiClient, err := config.InitAPIClient() apiClient, err := config.InitAPIClient()
if err != nil { if err != nil {
return err return err
} }
payload = sailpointbetasdk.NewExportPayload() if objectOptions != "" {
payload.Description = &description err = json.Unmarshal([]byte(objectOptions), &options)
payload.IncludeTypes = includeTypes if err != nil {
payload.ExcludeTypes = excludeTypes return err
}
}
job, _, err := apiClient.Beta.SPConfigApi.ExportSpConfig(context.TODO()).ExportPayload(*payload).Execute() job, _, err := apiClient.Beta.SPConfigApi.ExportSpConfig(context.TODO()).ExportPayload(beta.ExportPayload{Description: &description, IncludeTypes: includeTypes, ExcludeTypes: excludeTypes, ObjectOptions: options}).Execute()
if err != nil { if err != nil {
return err return err
} }
@@ -54,9 +68,10 @@ func newExportCmd() *cobra.Command {
} }
cmd.Flags().StringVarP(&folderPath, "folderPath", "f", "spconfig-exports", "folder path to save the search results in. If the directory doesn't exist, then it will be automatically created. (default is the current working directory)") cmd.Flags().StringVarP(&folderPath, "folderPath", "f", "spconfig-exports", "folder path to save the search results in. If the directory doesn't exist, then it will be automatically created. (default is the current working directory)")
cmd.Flags().StringVarP(&description, "description", "d", "", "optional description for the export job") cmd.Flags().StringVarP(&description, "description", "", "", "optional description for the export job")
cmd.Flags().StringArrayVarP(&includeTypes, "include", "i", []string{}, "types to include in export job") cmd.Flags().StringArrayVarP(&includeTypes, "include", "i", []string{}, "types to include in export job")
cmd.Flags().StringArrayVarP(&excludeTypes, "exclude", "e", []string{}, "types to exclude in export job") cmd.Flags().StringArrayVarP(&excludeTypes, "exclude", "e", []string{}, "types to exclude in export job")
cmd.Flags().StringVarP(&objectOptions, "objectOptions", "o", "", "options for the object types being exported")
cmd.Flags().BoolVarP(&wait, "wait", "w", false, "wait for the export job to finish, and download the results") cmd.Flags().BoolVarP(&wait, "wait", "w", false, "wait for the export job to finish, and download the results")
return cmd return cmd

44
cmd/spconfig/export.md Normal file
View File

@@ -0,0 +1,44 @@
==Long==
# Export
Start an Export job in IdentityNow
Valid Types that can be included or excluded are:
- ACCESS_PROFILE
- ACCESS_REQUEST_CONFIG
- ATTR_SYNC_SOURCE_CONFIG
- AUTH_ORG
- CAMPAIGN_FILTER
- FORM_DEFINITION
- GOVERNANCE_GROUP
- IDENTITY_OBJECT_CONFIG
- IDENTITY_PROFILE
- LIFECYCLE_STATE
- NOTIFICATION_TEMPLATE
- PASSWORD_POLICY
- PASSWORD_SYNC_GROUP
- PUBLIC_IDENTITIES_CONFIG
- ROLE
- RULE
- SERVICE_DESK_INTEGRATION
- SOD_POLICY
- SOURCE
- TRANSFORM
- TRIGGER_SUBSCRIPTION
- WORKFLOWS
====
==Example==
```bash
sail spconfig export --include WORKFLOWS --include SOURCE
sail spconfig export --include SOURCE --wait
sail spconfig export --include TRANSFORM --objectOptions '{
"TRANSFORM": {
"includedIds": [],
"includedNames": [
"Remove Diacritical Marks",
"Common - Location Lookup"
]
}
}'
```
====

View File

@@ -2,29 +2,34 @@
package spconfig package spconfig
import ( import (
"fmt" _ "embed"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func NewSPConfigCmd() *cobra.Command { //go:embed spconfig.md
var spconfigHelp string
func NewSPConfigCommand() *cobra.Command {
help := util.ParseHelp(spconfigHelp)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "spconfig", Use: "spconfig",
Short: "Perform SPConfig operations in IdentityNow", Short: "Perform SPConfig operations in IdentityNow",
Long: "\nPerform SPConfig operations in IdentityNow\n\n", Long: help.Long,
Example: "sail spconfig", Example: help.Example,
Aliases: []string{"spcon"}, Aliases: []string{"spcon"},
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
_, _ = fmt.Fprint(cmd.OutOrStdout(), cmd.UsageString()) cmd.Help()
}, },
} }
cmd.AddCommand( cmd.AddCommand(
newExportCmd(), newExportCommand(),
newStatusCmd(), newStatusCommand(),
newTemplateCmd(), newTemplateCommand(),
newDownloadCmd(), newDownloadCommand(),
newImportCommand(), newImportCommand(),
) )

16
cmd/spconfig/spconfig.md Normal file
View File

@@ -0,0 +1,16 @@
==Long==
# SPConfig
Perform SP-Config operations in IdentityNow
API References:
- https://developer.sailpoint.com/idn/api/beta/sp-config
====
==Example==
```bash
sail spconfig export --include WORKFLOWS --include SOURCE --wait
sail spconfig import
```
====

View File

@@ -19,7 +19,7 @@ func TestNewSPConfigCommand(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
cmd := NewSPConfigCmd() cmd := NewSPConfigCommand()
b := new(bytes.Buffer) b := new(bytes.Buffer)
cmd.SetOut(b) cmd.SetOut(b)

View File

@@ -9,7 +9,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newStatusCmd() *cobra.Command { func newStatusCommand() *cobra.Command {
var exportJobs []string var exportJobs []string
var importJobs []string var importJobs []string
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -26,20 +26,18 @@ func newStatusCmd() *cobra.Command {
return err return err
} }
for i := 0; i < len(exportJobs); i++ { for _, jobId := range exportJobs {
job := exportJobs[i]
status, _, err := apiClient.Beta.SPConfigApi.ExportSpConfigJobStatus(context.TODO(), job).Execute() //SPConfigApi.SpConfigExportJobStatus(ctx, job).Execute() status, _, err := apiClient.Beta.SPConfigApi.GetSpConfigExportStatus(context.TODO(), jobId).Execute()
if err != nil { if err != nil {
return err return err
} }
spconfig.PrintJob(*status) spconfig.PrintJob(*status)
} }
for i := 0; i < len(importJobs); i++ { for _, jobId := range importJobs {
job := importJobs[i]
status, _, err := apiClient.Beta.SPConfigApi.ImportSpConfigJobStatus(context.TODO(), job).Execute() status, _, err := apiClient.Beta.SPConfigApi.GetSpConfigImportStatus(context.TODO(), jobId).Execute()
if err != nil { if err != nil {
return err return err
} }
@@ -50,8 +48,8 @@ func newStatusCmd() *cobra.Command {
}, },
} }
cmd.Flags().StringArrayVarP(&importJobs, "import", "i", []string{}, "a list of import job ids to return the status of") cmd.Flags().StringArrayVarP(&importJobs, "import", "", []string{}, "a list of import job ids to return the status of")
cmd.Flags().StringArrayVarP(&exportJobs, "export", "e", []string{}, "a list of export job ids to return the status of") cmd.Flags().StringArrayVarP(&exportJobs, "export", "", []string{}, "a list of export job ids to return the status of")
return cmd return cmd
} }

View File

@@ -3,6 +3,7 @@ package spconfig
import ( import (
"context" "context"
_ "embed"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "strings"
@@ -13,19 +14,23 @@ import (
"github.com/sailpoint-oss/sailpoint-cli/internal/templates" "github.com/sailpoint-oss/sailpoint-cli/internal/templates"
"github.com/sailpoint-oss/sailpoint-cli/internal/terminal" "github.com/sailpoint-oss/sailpoint-cli/internal/terminal"
"github.com/sailpoint-oss/sailpoint-cli/internal/types" "github.com/sailpoint-oss/sailpoint-cli/internal/types"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newTemplateCmd() *cobra.Command { //go:embed template.md
var outputTypes []string var templateHelp string
func newTemplateCommand() *cobra.Command {
help := util.ParseHelp(templateHelp)
var folderPath string var folderPath string
var template string var template string
var wait bool var wait bool
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "template", Use: "template",
Short: "Begin an SPConfig Export task in IdentityNow using a template", Short: "Begin an SPConfig Export task in IdentityNow using a template",
Long: "\nBegin an SPConfig Export task in IdentityNow using a template\n\n", Long: help.Long,
Example: "sail spconfig template --wait", Example: help.Example,
Aliases: []string{"temp"}, Aliases: []string{"temp"},
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
@@ -91,7 +96,6 @@ func newTemplateCmd() *cobra.Command {
}, },
} }
cmd.Flags().StringArrayVarP(&outputTypes, "outputTypes", "o", []string{"json"}, "the sort value for the api call (examples)")
cmd.Flags().StringVarP(&folderPath, "folderPath", "f", "spconfig-exports", "folder path to save the search results in. If the directory doesn't exist, then it will be automatically created. (default is the current working directory)") cmd.Flags().StringVarP(&folderPath, "folderPath", "f", "spconfig-exports", "folder path to save the search results in. If the directory doesn't exist, then it will be automatically created. (default is the current working directory)")
cmd.Flags().BoolVarP(&wait, "wait", "w", false, "wait for the export job to finish, and download the results") cmd.Flags().BoolVarP(&wait, "wait", "w", false, "wait for the export job to finish, and download the results")

15
cmd/spconfig/template.md Normal file
View File

@@ -0,0 +1,15 @@
==Long==
# Template
Begin an SPConfig Export task in IdentityNow using a template
====
==Example==
```bash
sail spconfig template --wait
```
====

View File

@@ -19,7 +19,7 @@ func TestNewTemplateCommand(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
cmd := newTemplateCmd() cmd := newTemplateCommand()
b := new(bytes.Buffer) b := new(bytes.Buffer)
cmd.SetOut(b) cmd.SetOut(b)

View File

@@ -16,7 +16,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newCreateCmd() *cobra.Command { func newCreateCommand() *cobra.Command {
var filepath string var filepath string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "create", Use: "create",

View File

@@ -104,7 +104,7 @@ func TestNewCRUDCmd(t *testing.T) {
// ctrl := gomock.NewController(t) // ctrl := gomock.NewController(t)
// defer ctrl.Finish() // defer ctrl.Finish()
createCMD := newCreateCmd() createCMD := newCreateCommand()
createBuffer := new(bytes.Buffer) createBuffer := new(bytes.Buffer)
createCMD.SetOut(createBuffer) createCMD.SetOut(createBuffer)
@@ -134,7 +134,7 @@ func TestNewCRUDCmd(t *testing.T) {
t.Fatalf("Unable to save test data: %v", err) t.Fatalf("Unable to save test data: %v", err)
} }
cmd := newUpdateCmd() cmd := newUpdateCommand()
cmd.Flags().Set("file", PATH.Join(path, updateFile)) cmd.Flags().Set("file", PATH.Join(path, updateFile))
@@ -143,7 +143,7 @@ func TestNewCRUDCmd(t *testing.T) {
t.Fatalf("error execute cmd: %v", err) t.Fatalf("error execute cmd: %v", err)
} }
deleteCMD := newDeleteCmd() deleteCMD := newDeleteCommand()
deleteBuffer := new(bytes.Buffer) deleteBuffer := new(bytes.Buffer)
deleteCMD.SetOut(deleteBuffer) deleteCMD.SetOut(deleteBuffer)

View File

@@ -3,92 +3,27 @@ package transform
import ( import (
"context" "context"
"fmt"
"os"
"github.com/charmbracelet/bubbles/table"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/sailpoint-oss/sailpoint-cli/internal/config" "github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/transform"
tuitable "github.com/sailpoint-oss/sailpoint-cli/internal/tui/table"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newDeleteCmd() *cobra.Command { func newDeleteCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "delete", Use: "delete",
Short: "Delete an IdentityNow Transform", Short: "Delete an IdentityNow Transform",
Long: "\nDelete an IdentityNow Transform\n\n", Long: "\nDelete an IdentityNow Transform\n\n",
Example: "sail transform delete 03d5187b-ab96-402c-b5a1-40b74285d77a | sail transform delete", Example: "sail transform delete 03d5187b-ab96-402c-b5a1-40b74285d77a",
Aliases: []string{"d"}, Aliases: []string{"d"},
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
var id []string
apiClient, err := config.InitAPIClient() apiClient, err := config.InitAPIClient()
if err != nil { if err != nil {
return err return err
} }
if len(args) < 1 { for _, transformID := range args {
transforms, err := transform.GetTransforms(*apiClient)
if err != nil {
return err
}
columns := []table.Column{
{Title: "ID", Width: 40},
{Title: "Name", Width: 25},
{Title: "Type", Width: 25},
}
var rows []table.Row
for i := 0; i < len(transforms); i++ {
transform := transforms[i]
rows = append(rows, []string{*transform.Id, transform.Name, transform.Type})
}
t := table.New(
table.WithColumns(columns),
table.WithRows(rows),
table.WithFocused(true),
table.WithHeight(7),
)
s := table.DefaultStyles()
s.Header = s.Header.
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("240")).
BorderBottom(true).
Bold(false)
s.Selected = s.Selected.
Foreground(lipgloss.Color("229")).
Background(lipgloss.Color("57")).
Bold(false)
t.SetStyles(s)
m := tuitable.Model{Table: t}
if _, err := tea.NewProgram(m).Run(); err != nil {
fmt.Println("Error running program:", err)
os.Exit(1)
}
tempRow := m.Retrieve()
if len(tempRow) > 0 {
id = append(id, m.Retrieve()[0])
} else {
return fmt.Errorf("no transform selected")
}
} else {
id = args
}
for i := 0; i < len(id); i++ {
transformID := id[i]
_, err = apiClient.V3.TransformsApi.DeleteTransform(context.TODO(), transformID).Execute() _, err = apiClient.V3.TransformsApi.DeleteTransform(context.TODO(), transformID).Execute()
if err != nil { if err != nil {

View File

@@ -2,18 +2,21 @@
package transform package transform
import ( import (
"context"
"encoding/json" "encoding/json"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
sailpoint "github.com/sailpoint-oss/golang-sdk"
v3 "github.com/sailpoint-oss/golang-sdk/v3"
"github.com/sailpoint-oss/sailpoint-cli/internal/config" "github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/transform" "github.com/sailpoint-oss/sailpoint-cli/internal/sdk"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newDownloadCmd() *cobra.Command { func newDownloadCommand() *cobra.Command {
var destination string var destination string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "download", Use: "download",
@@ -29,14 +32,9 @@ func newDownloadCmd() *cobra.Command {
return err return err
} }
transforms, err := transform.GetTransforms(*apiClient) transforms, resp, err := sailpoint.PaginateWithDefaults[v3.Transform](apiClient.V3.TransformsApi.ListTransforms(context.TODO()))
if err != nil { if err != nil {
return err return sdk.HandleSDKError(resp, err)
}
err = transform.ListTransforms(transforms)
if err != nil {
return err
} }
for _, v := range transforms { for _, v := range transforms {

View File

@@ -20,7 +20,7 @@ func TestNewDownloadCmd(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
cmd := newListCmd() cmd := newListCommand()
b := new(bytes.Buffer) b := new(bytes.Buffer)
cmd.SetOut(b) cmd.SetOut(b)

View File

@@ -2,12 +2,17 @@
package transform package transform
import ( import (
"context"
sailpoint "github.com/sailpoint-oss/golang-sdk"
v3 "github.com/sailpoint-oss/golang-sdk/v3"
"github.com/sailpoint-oss/sailpoint-cli/internal/config" "github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/transform" "github.com/sailpoint-oss/sailpoint-cli/internal/output"
"github.com/sailpoint-oss/sailpoint-cli/internal/sdk"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newListCmd() *cobra.Command { func newListCommand() *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "list", Use: "list",
Short: "List all Transforms in IdentityNow", Short: "List all Transforms in IdentityNow",
@@ -22,15 +27,19 @@ func newListCmd() *cobra.Command {
return err return err
} }
transforms, err := transform.GetTransforms(*apiClient) transforms, resp, err := sailpoint.PaginateWithDefaults[v3.Transform](apiClient.V3.TransformsApi.ListTransforms(context.TODO()))
if err != nil { if err != nil {
return err return sdk.HandleSDKError(resp, err)
} }
err = transform.ListTransforms(transforms) var entries [][]string
if err != nil {
return err for _, v := range transforms {
entries = append(entries, []string{v.Name, *v.Id})
} }
output.WriteTable(cmd.OutOrStdout(), []string{"Name", "ID"}, entries)
return nil return nil
}, },
} }

View File

@@ -20,7 +20,7 @@ func TestNewListCmd(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
cmd := newListCmd() cmd := newListCommand()
b := new(bytes.Buffer) b := new(bytes.Buffer)
cmd.SetOut(b) cmd.SetOut(b)

View File

@@ -19,7 +19,7 @@ import (
var implicitInput bool var implicitInput bool
func newPreviewCmd() *cobra.Command { func newPreviewCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "preview", Use: "preview",
Short: "Preview the effects of an IdentityNow Transform", Short: "Preview the effects of an IdentityNow Transform",

View File

@@ -2,8 +2,6 @@
package transform package transform
import ( import (
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -14,7 +12,7 @@ const (
userEndpoint = "/cc/api/identity/list" userEndpoint = "/cc/api/identity/list"
) )
func NewTransformCmd() *cobra.Command { func NewTransformCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "transform", Use: "transform",
Short: "Manage Transforms in IdentityNow", Short: "Manage Transforms in IdentityNow",
@@ -22,19 +20,19 @@ func NewTransformCmd() *cobra.Command {
Example: "sail transform | sail tran", Example: "sail transform | sail tran",
Aliases: []string{"tran"}, Aliases: []string{"tran"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
_, _ = fmt.Fprint(cmd.OutOrStdout(), cmd.UsageString()) cmd.Help()
}, },
} }
cmd.PersistentFlags().StringP("transforms-endpoint", "e", transformsEndpoint, "Override transforms endpoint") cmd.PersistentFlags().StringP("transforms-endpoint", "e", transformsEndpoint, "Override transforms endpoint")
cmd.AddCommand( cmd.AddCommand(
newListCmd(), newListCommand(),
newDownloadCmd(), newDownloadCommand(),
newCreateCmd(), newCreateCommand(),
newUpdateCmd(), newUpdateCommand(),
newDeleteCmd(), newDeleteCommand(),
newPreviewCmd(), newPreviewCommand(),
) )
return cmd return cmd

View File

@@ -14,7 +14,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newUpdateCmd() *cobra.Command { func newUpdateCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "update", Use: "update",
Short: "Update a Transform in IdentityNow from a File", Short: "Update a Transform in IdentityNow from a File",

View File

@@ -1,41 +1,54 @@
package va package va
import ( import (
_ "embed"
"os" "os"
"sync" "sync"
"time" "time"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/sailpoint-oss/sailpoint-cli/internal/terminal" "github.com/sailpoint-oss/sailpoint-cli/internal/terminal"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/sailpoint-oss/sailpoint-cli/internal/va" "github.com/sailpoint-oss/sailpoint-cli/internal/va"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/vbauerster/mpb/v8" "github.com/vbauerster/mpb/v8"
) )
func newCollectCmd(term terminal.Terminal) *cobra.Command { //go:embed collect.md
var collectHelp string
func newCollectCommand(term terminal.Terminal) *cobra.Command {
help := util.ParseHelp(collectHelp)
var credentials []string var credentials []string
var output string var output string
var logs bool var logs bool
var config bool var config bool
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "collect", Use: "collect [-c | -l] [-o output] VA-Network-Address... [-p va-password]",
Short: "Collect Configuration or Log Files from a SailPoint Virtual Appliance", Short: "Collect files from a SailPoint Virtual Appliance",
Long: "\nCollect Configuration or Log Files from a SailPoint Virtual Appliance\n\n", Long: help.Long,
Example: "sail va collect 10.10.10.25, 10.10.10.26 -p S@ilp0int -p S@ilp0int \n\nLog Files:\n/home/sailpoint/log/ccg.log\n/home/sailpoint/log/charon.log\n/home/sailpoint/stuntlog.txt\n\nConfig Files:\n/home/sailpoint/proxy.yaml\n/etc/systemd/network/static.network\n/etc/resolv.conf\n", Example: help.Example,
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
var err error var err error
logFiles := []string{"/home/sailpoint/log/ccg.log", "/home/sailpoint/log/charon.log"}
configFiles := []string{"/home/sailpoint/proxy.yaml", "/etc/systemd/network/static.network", "/etc/resolv.conf"}
if output == "" { if output == "" {
output, _ = os.Getwd() output, _ = os.Getwd()
} }
var files []string var files []string
if logs { if logs {
files = []string{"/home/sailpoint/log/ccg.log", "/home/sailpoint/log/charon.log", "/home/sailpoint/stuntlog.txt"} files = append(files, logFiles...)
} else if config { }
files = []string{"/home/sailpoint/proxy.yaml", "/etc/systemd/network/static.network", "/etc/resolv.conf"} if config {
} else { files = append(files, configFiles...)
files = []string{"/home/sailpoint/log/ccg.log", "/home/sailpoint/log/charon.log", "/home/sailpoint/stuntlog.txt", "/home/sailpoint/proxy.yaml", "/etc/systemd/network/static.network", "/etc/resolv.conf"} }
if !config && !logs {
files = append(files, logFiles...)
files = append(files, configFiles...)
} }
var wg sync.WaitGroup var wg sync.WaitGroup
@@ -45,7 +58,12 @@ func newCollectCmd(term terminal.Terminal) *cobra.Command {
mpb.WithWaitGroup(&wg)) mpb.WithWaitGroup(&wg))
for i, endpoint := range args { for i, endpoint := range args {
password := credentials[i] var password string
if len(credentials) >= i-1 {
password = credentials[i]
}
if password == "" { if password == "" {
password, err = term.PromptPassword("Please enter the password for " + endpoint) password, err = term.PromptPassword("Please enter the password for " + endpoint)
if err != nil { if err != nil {
@@ -70,12 +88,12 @@ func newCollectCmd(term terminal.Terminal) *cobra.Command {
}, },
} }
cmd.Flags().StringVarP(&output, "Output", "o", "", "The path to save the log files") cmd.Flags().StringVarP(&output, "output", "o", "", "The path to save the log files")
cmd.Flags().BoolVarP(&logs, "logs", "l", false, "Retrieve log files") cmd.Flags().BoolVarP(&logs, "log", "l", false, "retrieve log files")
cmd.Flags().BoolVarP(&config, "config", "c", false, "Retrieve config files") cmd.Flags().BoolVarP(&config, "config", "c", false, "retrieve config files")
cmd.Flags().StringArrayVarP(&credentials, "Passwords", "p", []string{}, "You can enter the Passwords for the servers in the same order that the servers are listed as arguments") cmd.Flags().StringArrayVarP(&credentials, "passwords", "p", []string{}, "passwords for the servers in the same order that the servers are listed as arguments")
cmd.MarkFlagsMutuallyExclusive("config", "logs") cmd.MarkFlagsMutuallyExclusive("config", "log")
return cmd return cmd
} }

30
cmd/va/collect.md Normal file
View File

@@ -0,0 +1,30 @@
==Long==
# Collect
Collect files from a remote Virtual Appliance
Files are collected over SFTP. Passwords are provided via the --password (-p) flag or they will be prompted for at runtime. Server addresses can be DNS names or IP addresses, and are provided as arguments separated by spaces.
Log Files:
```bash
/home/sailpoint/log/ccg.log
/home/sailpoint/log/charon.log
```
Config Files:
```bash
/home/sailpoint/proxy.yaml
/etc/systemd/network/static.network
/etc/resolv.conf
```
====
==Example==
```bash
sail va collect 10.10.10.25 10.10.10.26 -p S@ilp0int -p S@ilp0int
sail va collect 10.10.10.25 --config
sail va collect 10.10.10.26 --log
sail va collect 10.10.10.25 --log --output log_files
sail va collect 10.10.10.25 --output all_files
```
====

69
cmd/va/get.go Normal file
View File

@@ -0,0 +1,69 @@
package va
import (
"context"
_ "embed"
sailpoint "github.com/sailpoint-oss/golang-sdk"
"github.com/sailpoint-oss/golang-sdk/beta"
"github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/sdk"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)
//go:embed get.md
var getHelp string
func newGetCommand() *cobra.Command {
help := util.ParseHelp(getHelp)
cmd := &cobra.Command{
Use: "get",
Short: "Get a Virtual Appliance configuration from IdentityNow",
Long: help.Long,
Example: help.Example,
RunE: func(cmd *cobra.Command, args []string) error {
apiClient, err := config.InitAPIClient()
if err != nil {
return err
}
var ClientIDs []string
var VAs []beta.ManagedClientStatus
clusters, resp, clustersErr := sailpoint.PaginateWithDefaults[beta.ManagedCluster](apiClient.Beta.ManagedClustersApi.GetManagedClusters(context.TODO()))
if clustersErr != nil {
return sdk.HandleSDKError(resp, clustersErr)
}
for _, cluster := range clusters {
for _, id := range cluster.ClientIds {
if len(args) > 0 {
if slices.Contains(args, id) {
ClientIDs = append(ClientIDs, id)
}
} else {
ClientIDs = append(ClientIDs, id)
}
}
}
for _, id := range ClientIDs {
clientStatus, resp, clientErr := apiClient.Beta.ManagedClientsApi.GetManagedClientStatus(context.TODO(), id).Type_("VA").Execute()
if clientErr != nil {
return sdk.HandleSDKError(resp, clientErr)
}
VAs = append(VAs, *clientStatus)
}
cmd.Println(util.PrettyPrint(VAs))
return nil
},
}
return cmd
}

13
cmd/va/get.md Normal file
View File

@@ -0,0 +1,13 @@
==Long==
# Get
Get a Virtual Appliance configuration from IdentityNow
====
==Example==
```bash
sail va get
```
====

View File

@@ -2,21 +2,27 @@ package va
import ( import (
"context" "context"
_ "embed"
sailpoint "github.com/sailpoint-oss/golang-sdk" sailpoint "github.com/sailpoint-oss/golang-sdk"
sailpointbetasdk "github.com/sailpoint-oss/golang-sdk/beta" "github.com/sailpoint-oss/golang-sdk/beta"
"github.com/sailpoint-oss/sailpoint-cli/internal/config" "github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/output"
"github.com/sailpoint-oss/sailpoint-cli/internal/sdk" "github.com/sailpoint-oss/sailpoint-cli/internal/sdk"
"github.com/sailpoint-oss/sailpoint-cli/internal/util" "github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newListCmd() *cobra.Command { //go:embed list.md
var listHelp string
func newListCommand() *cobra.Command {
help := util.ParseHelp(listHelp)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "list", Use: "list",
Short: "List the Clusters and Virtual Appliances configured in IdentityNow", Short: "List the Virtual Appliances configured in IdentityNow",
Long: "\nList the Clusters and Virtual Appliances configured in IdentityNow\n\n", Long: help.Long,
Example: "sail va list", Example: help.Example,
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
@@ -25,12 +31,27 @@ func newListCmd() *cobra.Command {
return err return err
} }
clusters, resp, err := sailpoint.PaginateWithDefaults[sailpointbetasdk.ManagedCluster](apiClient.Beta.ManagedClustersApi.GetManagedClusters(context.TODO())) clusters, resp, clustersErr := sailpoint.PaginateWithDefaults[beta.ManagedCluster](apiClient.Beta.ManagedClustersApi.GetManagedClusters(context.TODO()))
if err != nil { if clustersErr != nil {
return sdk.HandleSDKError(resp, err) return sdk.HandleSDKError(resp, clustersErr)
} }
cmd.Println(util.PrettyPrint(clusters)) var clients [][]string
for _, cluster := range clusters {
for _, id := range cluster.ClientIds {
clientStatus, resp, clientErr := apiClient.Beta.ManagedClientsApi.GetManagedClientStatus(context.TODO(), id).Type_("VA").Execute()
if clientErr != nil {
return sdk.HandleSDKError(resp, clientErr)
}
if clientStatus.Status != "NOT_CONFIGURED" {
clients = append(clients, []string{*cluster.Name, clientStatus.Body["internal_ip"].(string), clientStatus.Body["id"].(string)})
}
}
}
output.WriteTable(cmd.OutOrStdout(), []string{"Cluster", "IP Address", "ID"}, clients)
return nil return nil
}, },

13
cmd/va/list.md Normal file
View File

@@ -0,0 +1,13 @@
==Long==
# List
List the Clusters and Virtual Appliances in the configured IdentityNow tenant
====
==Example==
```bash
sail va list
```
====

View File

@@ -3,6 +3,7 @@ package va
import ( import (
"bufio" "bufio"
"bytes" "bytes"
_ "embed"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -13,6 +14,7 @@ import (
"time" "time"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/vbauerster/mpb/v8" "github.com/vbauerster/mpb/v8"
"github.com/vbauerster/mpb/v8/decor" "github.com/vbauerster/mpb/v8/decor"
@@ -174,7 +176,7 @@ func ErrorCheck(token []byte) bool {
return bytes.Contains(token, errorString) || bytes.Contains(token, exceptionString) return bytes.Contains(token, errorString) || bytes.Contains(token, exceptionString)
} }
func ParseCCGFile(p *mpb.Progress, filepath string, everything bool) error { func ParseCCGFile(p *mpb.Progress, filepath string, all bool) error {
file, err := os.Open(filepath) file, err := os.Open(filepath)
if err != nil { if err != nil {
return err return err
@@ -230,7 +232,7 @@ func ParseCCGFile(p *mpb.Progress, filepath string, everything bool) error {
if err != nil { if err != nil {
break break
} else { } else {
if ErrorCheck(token) || everything { if ErrorCheck(token) || all {
var line CCG var line CCG
unErr := json.Unmarshal(token, &line) unErr := json.Unmarshal(token, &line)
if unErr == nil && line.Org != "" { if unErr == nil && line.Org != "" {
@@ -250,7 +252,7 @@ func ParseCCGFile(p *mpb.Progress, filepath string, everything bool) error {
return nil return nil
} }
func ParseCanalFile(p *mpb.Progress, filepath string, everything bool) error { func ParseCanalFile(p *mpb.Progress, filepath string, all bool) error {
file, err := os.Open(filepath) file, err := os.Open(filepath)
if err != nil { if err != nil {
return err return err
@@ -318,18 +320,23 @@ func ParseCanalFile(p *mpb.Progress, filepath string, everything bool) error {
return nil return nil
} }
func newParseCmd() *cobra.Command { //go:embed parse.md
var ccg bool var parseHelp string
var canal bool
var everything bool func newParseCommand() *cobra.Command {
help := util.ParseHelp(parseHelp)
var fileType string
var all bool
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "parse", Use: "parse",
Short: "Parse Log Files from SailPoint Virtual Appliances", Short: "Parse Log Files from SailPoint Virtual Appliances",
Long: "\nParse Log Files from SailPoint Virtual Appliances\n\n", Long: help.Long,
Example: "sail va parse ./path/to/ccg.log ./path/to/ccg.log ./path/to/canal.log ./path/to/canal.log", Example: help.Example,
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if ccg || canal {
if fileType != "" {
var wg sync.WaitGroup var wg sync.WaitGroup
p := mpb.New( p := mpb.New(
@@ -341,43 +348,41 @@ func newParseCmd() *cobra.Command {
log.Info("Parsing Log Files", "files", args) log.Info("Parsing Log Files", "files", args)
log.SetOutput(p) log.SetOutput(p)
for i := 0; i < len(args); i++ { for _, filepath := range args {
wg.Add(1) wg.Add(1)
filepath := args[i] switch fileType {
case "ccg":
if ccg { go func(filepath string) {
go func() {
defer wg.Done() defer wg.Done()
err := ParseCCGFile(p, filepath, everything) err := ParseCCGFile(p, filepath, all)
if err != nil { if err != nil {
log.Error("Issue Parsing log file", "file", filepath, "error", err) log.Error("Issue Parsing log file", "file", filepath, "error", err)
} }
}() }(filepath)
} else if canal { case "canal":
go func() { go func(filepath string) {
defer wg.Done() defer wg.Done()
err := ParseCanalFile(p, filepath, everything) err := ParseCanalFile(p, filepath, all)
if err != nil { if err != nil {
log.Error("Issue Parsing log file", "file", filepath, "error", err) log.Error("Issue Parsing log file", "file", filepath, "error", err)
} }
}() }(filepath)
} }
}
wg.Wait()
return nil }
wg.Wait()
} else { } else {
return errors.New("must specify either ccg or canal") cmd.Help()
} }
return nil
}, },
} }
cmd.Flags().BoolVarP(&ccg, "ccg", "", false, "Specifies the provided files are CCG Files") cmd.Flags().StringVarP(&fileType, "type", "t", "", "Specifies the log type to parse (ccg, canal)")
cmd.Flags().BoolVarP(&canal, "canal", "", false, "Specifies the provided files are CANAL Files") cmd.Flags().BoolVarP(&all, "all", "a", false, "Specifies all log traffic should be parsed, not just errors")
cmd.Flags().BoolVarP(&everything, "everything", "e", false, "Specifies all log traffic should be parsed, not just errors")
cmd.MarkFlagsMutuallyExclusive("ccg", "canal")
cmd.MarkFlagsMutuallyExclusive("everything", "canal")
return cmd return cmd
} }

25
cmd/va/parse.md Normal file
View File

@@ -0,0 +1,25 @@
==Long==
# Parse
Parse Log Files from SailPoint Virtual Appliances
====
==Example==
## Parsing CCG Logs:
All the errors will be parsed out of the log file and sorted by date and connector name.
Supplying the `--all` flag will parse all the log traffic out, not just errors.
```bash
sail va parse --type ccg ./path/to/ccg.log ./path/to/ccg.log
sail va parse --type ccg ./path/to/ccg.log ./path/to/ccg.log --all
```
## Parsing CANAL Logs:
```bash
sail va parse --type canal ./path/to/canal.log ./path/to/canal.log
```
====

View File

@@ -36,8 +36,7 @@ func NewTroubleshootCmd(term terminal.Terminal) *cobra.Command {
credentials = append(credentials, password) credentials = append(credentials, password)
} }
for host := 0; host < len(args); host++ { for index, endpoint := range args {
endpoint := args[host]
outputDir := path.Join(output, endpoint) outputDir := path.Join(output, endpoint)
if _, err := os.Stat(outputDir); errors.Is(err, os.ErrNotExist) { if _, err := os.Stat(outputDir); errors.Is(err, os.ErrNotExist) {
@@ -47,16 +46,13 @@ func NewTroubleshootCmd(term terminal.Terminal) *cobra.Command {
} }
} }
password := credentials[host] password := credentials[index]
orgErr := va.RunVACmdLive(endpoint, password, TroubleshootingScript) orgErr := va.RunVACmdLive(endpoint, password, TroubleshootingScript)
if orgErr != nil { if orgErr != nil {
return orgErr return orgErr
} }
color.Green("Troubleshooting Complete")
color.Blue("Collecting stuntlog")
var wg sync.WaitGroup var wg sync.WaitGroup
p := mpb.New(mpb.WithWidth(60), p := mpb.New(mpb.WithWidth(60),
mpb.PopCompletedMode(), mpb.PopCompletedMode(),

0
cmd/va/troubleshoot.md Normal file
View File

View File

@@ -19,7 +19,7 @@ func updateAndRebootVA(endpoint, password string) {
log.Info("Virtual Appliance Updating", "VA", endpoint) log.Info("Virtual Appliance Updating", "VA", endpoint)
reboot, rebootErr := va.RunVACmd(endpoint, password, RebootCommand) reboot, rebootErr := va.RunVACmd(endpoint, password, RebootCommand)
if rebootErr != nil && rebootErr.Error() != "wait: remote command exited without exit status or exit signal" { if rebootErr != nil && rebootErr.Error() != "wait: remote command exited without exit status or exit signal" {
log.Error("Problem Rebooting", "VA", endpoint, "err", rebootErr, "resp", reboot) log.Error("Problem Rebooting", "Server", endpoint, "err", rebootErr, "resp", reboot)
} else { } else {
log.Info("Virtual Appliance Rebooting", "VA", endpoint) log.Info("Virtual Appliance Rebooting", "VA", endpoint)
} }
@@ -28,7 +28,7 @@ func updateAndRebootVA(endpoint, password string) {
fmt.Println() fmt.Println()
} }
func newUpdateCmd(term terminal.Terminal) *cobra.Command { func newUpdateCommand(term terminal.Terminal) *cobra.Command {
var credentials []string var credentials []string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "update", Use: "update",

11
cmd/va/update.md Normal file
View File

@@ -0,0 +1,11 @@
==Long==
# Update
Update a SailPoint Virtual Appliance
====
==Example==
```bash
sail va update 10.10.10.25
```
====

View File

@@ -2,31 +2,35 @@
package va package va
import ( import (
"fmt" _ "embed"
"github.com/sailpoint-oss/sailpoint-cli/cmd/va/logConfig"
"github.com/sailpoint-oss/sailpoint-cli/internal/terminal" "github.com/sailpoint-oss/sailpoint-cli/internal/terminal"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func NewVACmd(term terminal.Terminal) *cobra.Command { //go:embed va.md
var vaHelp string
func NewVACommand(term terminal.Terminal) *cobra.Command {
help := util.ParseHelp(vaHelp)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "va", Use: "va",
Short: "Interact with SailPoint Virtual Appliances", Short: "Manage SailPoint Virtual Appliances",
Long: "\nInteract with SailPoint Virtual Appliances\n\n", Long: help.Long,
Aliases: []string{"va"}, Example: help.Example,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
_, _ = fmt.Fprint(cmd.OutOrStdout(), cmd.UsageString()) cmd.Help()
}, },
} }
cmd.AddCommand( cmd.AddCommand(
newCollectCmd(term), newCollectCommand(term),
// newTroubleshootCmd(), // newTroubleshootCommand(),
newListCmd(), newGetCommand(),
newParseCmd(), newParseCommand(),
newUpdateCmd(term), newUpdateCommand(term),
logConfig.NewLogCmd(), newListCommand(),
) )
return cmd return cmd

14
cmd/va/va.md Normal file
View File

@@ -0,0 +1,14 @@
==Long==
# VA
Manage Virtual Appliances in IdentityNow
====
==Example==
```bash
sail va collect 10.10.10.25
sail va list
```
====

121
cmd/workflow/create.go Normal file
View File

@@ -0,0 +1,121 @@
// Copyright (c) 2023, SailPoint Technologies, Inc. All rights reserved.
package workflow
import (
"context"
_ "embed"
"encoding/json"
"os"
"strings"
"github.com/sailpoint-oss/golang-sdk/beta"
"github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/sdk"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra"
)
//go:embed create.md
var createHelp string
func newCreateCommand() *cobra.Command {
help := util.ParseHelp(createHelp)
var file bool
var directory bool
cmd := &cobra.Command{
Use: "create [-f file1 file2 ... | -d workflowDirectory ]",
Short: "Create Workflows in IdentityNow",
Long: help.Long,
Example: help.Example,
Aliases: []string{"cr"},
RunE: func(cmd *cobra.Command, args []string) error {
apiClient, err := config.InitAPIClient()
if err != nil {
return err
}
var workflows []beta.Workflow
var returnedWorkflows []beta.Workflow
var workflowFiles []string
if directory {
for _, workflowDirectory := range args {
files, err := os.ReadDir(workflowDirectory)
if err != nil {
return err
}
for _, file := range files {
if !file.IsDir() && strings.Contains(file.Name(), ".json") {
workflowFiles = append(workflowFiles, file.Name())
}
}
}
} else if file {
workflowFiles = args
} else {
cmd.Help()
return nil
}
for _, filePath := range workflowFiles {
file, err := os.OpenFile(filePath, os.O_RDONLY, os.ModePerm)
if err != nil {
return err
}
decoder := json.NewDecoder(file)
decoder.DisallowUnknownFields()
var workflow beta.Workflow
err = decoder.Decode(&workflow)
if err != nil {
return err
}
workflows = append(workflows, workflow)
}
for _, workflow := range workflows {
body, err := workflow.MarshalJSON()
if err != nil {
return err
}
createReq := beta.CreateWorkflowRequest{}
err = createReq.UnmarshalJSON(body)
if err != nil {
return err
}
workflowResp, resp, sdkErr := apiClient.Beta.WorkflowsApi.CreateWorkflow(context.TODO()).CreateWorkflowRequest(createReq).Execute()
if sdkErr != nil {
err := sdk.HandleSDKError(resp, sdkErr)
if err != nil {
return err
}
}
returnedWorkflows = append(returnedWorkflows, *workflowResp)
}
cmd.Println(util.PrettyPrint(returnedWorkflows))
return nil
},
}
cmd.Flags().BoolVarP(&file, "file", "f", false, "specifies that workflow file paths are provided as arguments to be created")
cmd.Flags().BoolVarP(&directory, "directory", "d", false, "specifies that a directory of workflows is provided to be created")
cmd.MarkFlagsMutuallyExclusive("file", "directory")
return cmd
}

25
cmd/workflow/create.md Normal file
View File

@@ -0,0 +1,25 @@
==Long==
# Create
Create Workflows in IdentityNow
## API References:
- https://developer.sailpoint.com/idn/api/beta/create-workflow
====
==Example==
## File Paths:
**Note:** File paths are relative to the current working directory, and only one workflow is allowed per file path. Multiple Workflows can be provided by specifying multiple file paths as arguments.
```bash
sail workflow create -f {file-path}
sail workflow create -f {file-path} {file-path} ...
```
## Folder Paths:
```bash
sail workflow create -d {folder-path}
sail workflow create -d {folder-path} {folder-path} ...
```
====

65
cmd/workflow/delete.go Normal file
View File

@@ -0,0 +1,65 @@
// Copyright (c) 2023, SailPoint Technologies, Inc. All rights reserved.
package workflow
import (
"context"
_ "embed"
"github.com/charmbracelet/log"
"github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/sdk"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra"
)
//go:embed delete.md
var deleteHelp string
func newDeleteCommand() *cobra.Command {
help := util.ParseHelp(deleteHelp)
cmd := &cobra.Command{
Use: "delete workflowID... ",
Short: "Delete a Workflow in IdentityNow",
Long: help.Long,
Example: help.Example,
Aliases: []string{"del"},
RunE: func(cmd *cobra.Command, args []string) error {
apiClient, err := config.InitAPIClient()
if err != nil {
return err
}
if len(args) > 0 {
for _, id := range args {
resp, sdkErr := apiClient.Beta.WorkflowsApi.DeleteWorkflow(context.TODO(), id).Execute()
if sdkErr != nil {
err := sdk.HandleSDKError(resp, sdkErr)
if err != nil {
return err
}
}
if resp.StatusCode == 204 {
log.Info("Workflow deleted", "id", id)
} else {
log.Warn("Workflow delete failed", "id", id)
}
}
} else {
cmd.Help()
return nil
}
return nil
},
}
return cmd
}

17
cmd/workflow/delete.md Normal file
View File

@@ -0,0 +1,17 @@
==Long==
# Delete
Delete a workflow in IdentityNow
## API References:
- https://developer.sailpoint.com/idn/api/beta/delete-workflow
====
==Example==
## Arguments:
```bash
sail workflow delete id1
sail workflow delete id1 id2 ...
sail workflow del $(cat list_of_workflowIDs.txt)
```
====

82
cmd/workflow/download.go Normal file
View File

@@ -0,0 +1,82 @@
// Copyright (c) 2023, SailPoint Technologies, Inc. All rights reserved.
package workflow
import (
"context"
_ "embed"
"os"
"path"
clean "github.com/mrz1836/go-sanitize"
"github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/sdk"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra"
)
//go:embed download.md
var downloadHelp string
func newDownloadCommand() *cobra.Command {
help := util.ParseHelp(downloadHelp)
var folderPath string
cmd := &cobra.Command{
Use: "download",
Short: "Download Workflows from IdentityNow",
Long: help.Long,
Example: help.Example,
Aliases: []string{"down"},
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
apiClient, err := config.InitAPIClient()
if err != nil {
return err
}
workflows, resp, sdkErr := apiClient.Beta.WorkflowsApi.ListWorkflows(context.TODO()).Execute()
if sdkErr != nil {
err := sdk.HandleSDKError(resp, sdkErr)
if err != nil {
return err
}
}
for _, v := range workflows {
fileName := clean.PathName(*v.Name) + ".json"
fullPath := path.Join(folderPath, fileName)
err := os.MkdirAll(folderPath, os.ModePerm)
if err != nil {
return err
}
file, err := os.Create(fullPath)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(util.PrettyPrint(v))
if err != nil {
return err
}
err = file.Sync()
if err != nil {
return err
}
}
return nil
},
}
cmd.Flags().StringVarP(&folderPath, "folder", "f", "workflows", "Folder to save the Workflows to")
return cmd
}

17
cmd/workflow/download.md Normal file
View File

@@ -0,0 +1,17 @@
==Long==
# Download
Downloads all Workflows from IdentityNow
## API References:
- https://developer.sailpoint.com/idn/api/beta/list-workflows
====
==Example==
```bash
sail workflow download
sail workflow download -f my-workflows
```
====

61
cmd/workflow/get.go Normal file
View File

@@ -0,0 +1,61 @@
// Copyright (c) 2023, SailPoint Technologies, Inc. All rights reserved.
package workflow
import (
"context"
_ "embed"
"github.com/sailpoint-oss/golang-sdk/beta"
"github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/sdk"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)
//go:embed get.md
var getHelp string
func newGetCommand() *cobra.Command {
help := util.ParseHelp(getHelp)
cmd := &cobra.Command{
Use: "get",
Short: "Get Workflows in IdentityNow",
Long: help.Long,
Example: help.Example,
Aliases: []string{"g"},
RunE: func(cmd *cobra.Command, args []string) error {
apiClient, err := config.InitAPIClient()
if err != nil {
return err
}
workflows, resp, sdkErr := apiClient.Beta.WorkflowsApi.ListWorkflows(context.TODO()).Execute()
if sdkErr != nil {
err := sdk.HandleSDKError(resp, sdkErr)
if err != nil {
return err
}
}
if len(args) > 0 {
var filteredList []beta.Workflow
for _, workflow := range workflows {
if slices.Contains(args, *workflow.Id) {
filteredList = append(filteredList, workflow)
}
}
workflows = filteredList
}
cmd.Println(util.PrettyPrint(workflows))
return nil
},
}
return cmd
}

17
cmd/workflow/get.md Normal file
View File

@@ -0,0 +1,17 @@
==Long==
# Get
Get workflows from IdentityNow.
## API References
- https://developer.sailpoint.com/idn/api/beta/list-workflows
====
==Example==
```bash
sail workflow get
sail workflow get f691874a-c5a5-426d-9dd4-33129072bafa
```
====

55
cmd/workflow/list.go Normal file
View File

@@ -0,0 +1,55 @@
// Copyright (c) 2023, SailPoint Technologies, Inc. All rights reserved.
package workflow
import (
"context"
_ "embed"
"github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/output"
"github.com/sailpoint-oss/sailpoint-cli/internal/sdk"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra"
)
//go:embed list.md
var listHelp string
func newListCommand() *cobra.Command {
help := util.ParseHelp(listHelp)
cmd := &cobra.Command{
Use: "list",
Short: "List all Workflows in IdentityNow",
Long: help.Long,
Example: help.Example,
Aliases: []string{"ls"},
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
apiClient, err := config.InitAPIClient()
if err != nil {
return err
}
workflows, resp, sdkErr := apiClient.Beta.WorkflowsApi.ListWorkflows(context.TODO()).Execute()
if sdkErr != nil {
err := sdk.HandleSDKError(resp, sdkErr)
if err != nil {
return err
}
}
var tableList [][]string
for _, entry := range workflows {
tableList = append(tableList, []string{*entry.Name, *entry.Id})
}
output.WriteTable(cmd.OutOrStdout(), []string{"Name", "ID"}, tableList)
return nil
},
}
return cmd
}

16
cmd/workflow/list.md Normal file
View File

@@ -0,0 +1,16 @@
==Long==
# List
List workflows from IdentityNow.
## API References
- https://developer.sailpoint.com/idn/api/beta/list-workflows
====
==Example==
```bash
sail Workflow list
```
====

102
cmd/workflow/update.go Normal file
View File

@@ -0,0 +1,102 @@
// Copyright (c) 2023, SailPoint Technologies, Inc. All rights reserved.
package workflow
import (
"context"
_ "embed"
"os"
"strings"
"github.com/sailpoint-oss/golang-sdk/beta"
"github.com/sailpoint-oss/sailpoint-cli/internal/config"
"github.com/sailpoint-oss/sailpoint-cli/internal/sdk"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra"
)
//go:embed update.md
var updateHelp string
func newUpdateCommand() *cobra.Command {
help := util.ParseHelp(updateHelp)
var file bool
var directory bool
cmd := &cobra.Command{
Use: "update",
Short: "Update a Workflow in IdentityNow",
Long: help.Long,
Example: help.Example,
Aliases: []string{"up"},
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var workflowFiles []string
var workflowList []beta.Workflow
apiClient, err := config.InitAPIClient()
if err != nil {
return err
}
if directory {
for _, workflowDirectory := range args {
files, err := os.ReadDir(workflowDirectory)
if err != nil {
return err
}
for _, file := range files {
if !file.IsDir() && strings.Contains(file.Name(), ".json") {
workflowFiles = append(workflowFiles, file.Name())
}
}
}
} else if file {
workflowFiles = args
} else {
cmd.Help()
return nil
}
for _, workflowFile := range workflowFiles {
var workflow beta.Workflow
contents, err := os.ReadFile(workflowFile)
if err != nil {
return err
}
workflow.UnmarshalJSON(contents)
workflowList = append(workflowList, workflow)
}
for _, workflowEntry := range workflowList {
body, err := workflowEntry.MarshalJSON()
if err != nil {
return err
}
workFlowBody := beta.WorkflowBody{}
workFlowBody.UnmarshalJSON(body)
returnedWorkflow, resp, sdkErr := apiClient.Beta.WorkflowsApi.UpdateWorkflow(context.TODO(), *workflowEntry.Id).WorkflowBody(workFlowBody).Execute()
if sdkErr != nil {
err := sdk.HandleSDKError(resp, sdkErr)
if err != nil {
return err
}
}
cmd.Println(util.PrettyPrint(returnedWorkflow))
}
return nil
},
}
cmd.Flags().BoolVarP(&file, "file", "f", false, "Read workflow from file(s)")
cmd.Flags().BoolVarP(&directory, "directory", "d", false, "Read workflow from stdin")
cmd.MarkFlagsMutuallyExclusive("file", "directory")
return cmd
}

23
cmd/workflow/update.md Normal file
View File

@@ -0,0 +1,23 @@
==Long==
# Update
Update a Workflow in IdentityNow
Arguments can be a list of directories or files.
If a directory is specified, all JSON files in the directory will be parsed and the workflows uploaded.
## API References:
- https://developer.sailpoint.com/idn/api/beta/update-workflow
====
==Example==
## File:
```bash
sail Workflow update -f {file-path} {file-path}
```
## Directory:
```bash
sail Workflow update -d {folder-path} {folder-path}
```
====

37
cmd/workflow/workflow.go Normal file
View File

@@ -0,0 +1,37 @@
// Copyright (c) 2023, SailPoint Technologies, Inc. All rights reserved.
package workflow
import (
_ "embed"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra"
)
//go:embed workflow.md
var workflowHelp string
func NewWorkflowCommand() *cobra.Command {
help := util.ParseHelp(workflowHelp)
cmd := &cobra.Command{
Use: "workflow",
Short: "Manage Workflows in IdentityNow",
Long: help.Long,
Example: help.Example,
Aliases: []string{"work"},
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
cmd.AddCommand(
newListCommand(),
newDownloadCommand(),
newCreateCommand(),
newUpdateCommand(),
newDeleteCommand(),
newGetCommand(),
)
return cmd
}

15
cmd/workflow/workflow.md Normal file
View File

@@ -0,0 +1,15 @@
==Long==
# Workflows
Manage Workflows in IdentityNow
====
==Example==
```bash
sail workflow list
sail workflow download
sail workflow update -f ./workflow-file.json
```
====

Some files were not shown because too many files have changed in this diff Show More