From 3933e6a12399bc21e40d3a94e69d4cd23b7f7cfe Mon Sep 17 00:00:00 2001 From: Sergiu Ghitea <28300158+sergiught@users.noreply.github.com> Date: Thu, 13 Apr 2023 14:10:14 +0200 Subject: [PATCH] Refactor and improve users import cmd --- docs/auth0_users_import.md | 13 ++- internal/cli/users.go | 145 +++++++++++++------------ internal/cli/users_test.go | 21 +--- test/integration/users-test-cases.yaml | 10 ++ 4 files changed, 96 insertions(+), 93 deletions(-) diff --git a/docs/auth0_users_import.md b/docs/auth0_users_import.md index 5aecb71b7..6de358746 100644 --- a/docs/auth0_users_import.md +++ b/docs/auth0_users_import.md @@ -18,9 +18,15 @@ auth0 users import [flags] ``` auth0 users import auth0 users import --connection "Username-Password-Authentication" + auth0 users import --connection "Username-Password-Authentication" --users-body "[]" + auth0 users import --connection "Username-Password-Authentication" --users-body "$(cat path/to/users.json)" auth0 users import -c "Username-Password-Authentication" --template "Basic Example" - auth0 users import -c "Username-Password-Authentication" -t "Basic Example" --upsert true - auth0 users import -c "Username-Password-Authentication" -t "Basic Example" --upsert true --email-results false + auth0 users import -c "Username-Password-Authentication" --users-body "$(cat path/to/users.json)" --upsert --email-results + auth0 users import -c "Username-Password-Authentication" --users-body "$(cat path/to/users.json)" --upsert --email-results --no-input + auth0 users import -c "Username-Password-Authentication" -b "$(cat path/to/users.json)" -u -r + auth0 users import -c "Username-Password-Authentication" -t "Basic Example" --upsert --email-results + auth0 users import -c "Username-Password-Authentication" -t "Basic Example" --upsert=false --email-results=false + auth0 users import -c "Username-Password-Authentication" -t "Basic Example" -u=false -r=false ``` @@ -29,8 +35,9 @@ auth0 users import [flags] ``` -c, --connection string Name of the database connection this user should be created in. -r, --email-results When true, sends a completion email to all tenant owners when the job is finished. The default is true, so you must explicitly set this parameter to false if you do not want emails sent. (default true) - -t, --template string Name of JSON example to be used. + -t, --template string Name of JSON example to be used. Cannot be used if the '--users-body' flag is passed. Options include: 'Empty', 'Basic Example', 'Custom Password Hash Example' and 'MFA Factors Example'. -u, --upsert When set to false, pre-existing users that match on email address, user ID, or username will fail. When set to true, pre-existing users that match on any of these fields will be updated, but only with upsertable attributes. + -b, --users-body string JSON template body that contains an array of user(s) to be imported. Cannot be used if the '--template' flag is passed. ``` diff --git a/internal/cli/users.go b/internal/cli/users.go index bcbf2899b..ff85d9183 100644 --- a/internal/cli/users.go +++ b/internal/cli/users.go @@ -76,17 +76,18 @@ var ( Help: "Number of users, that match the search criteria, to retrieve. Minimum 1, maximum 1000. If limit is hit, refine the search query.", } userImportTemplate = Flag{ - Name: "Template", - LongForm: "template", - ShortForm: "t", - Help: "Name of JSON example to be used.", + Name: "Template", + LongForm: "template", + ShortForm: "t", + Help: "Name of JSON example to be used. Cannot be used if the '--users-body' flag is passed. " + + "Options include: 'Empty', 'Basic Example', 'Custom Password Hash Example' and 'MFA Factors Example'.", IsRequired: false, } - userImportTemplateBody = Flag{ - Name: "Template Body", - LongForm: "template-body", + userImportBody = Flag{ + Name: "Users Body", + LongForm: "users-body", ShortForm: "b", - Help: "JSON template body that contains an array of user(s) to be imported.", + Help: "JSON template body that contains an array of user(s) to be imported. Cannot be used if the '--template' flag is passed.", IsRequired: false, } userEmailResults = Flag{ @@ -229,9 +230,8 @@ func createUserCmd(cli *cli) *cobra.Command { auth0 users create --name "John Doe" --email john@example.com --connection "Username-Password-Authentication" --username "example" auth0 users create -n "John Doe" -e john@example.com -c "Username-Password-Authentication" -u "example" --json`, RunE: func(cmd *cobra.Command, args []string) error { - // Select from the available connection types - // Users API currently support database connections - options, err := cli.connectionPickerOptions() + // Users API currently only supports database connections. + options, err := cli.dbConnectionPickerOptions() if err != nil { return err } @@ -537,7 +537,7 @@ func importUsersCmd(cli *cli) *cobra.Command { Connection string ConnectionID string Template string - TemplateBody string + UsersBody string Upsert bool SendCompletionEmail bool } @@ -549,76 +549,82 @@ func importUsersCmd(cli *cli) *cobra.Command { The file size limit for a bulk import is 500KB. You will need to start multiple imports if your data exceeds this size.`, Example: ` auth0 users import auth0 users import --connection "Username-Password-Authentication" + auth0 users import --connection "Username-Password-Authentication" --users-body "[]" + auth0 users import --connection "Username-Password-Authentication" --users-body "$(cat path/to/users.json)" auth0 users import -c "Username-Password-Authentication" --template "Basic Example" - auth0 users import -c "Username-Password-Authentication" -t "Basic Example" --upsert true - auth0 users import -c "Username-Password-Authentication" -t "Basic Example" --upsert true --email-results false`, + auth0 users import -c "Username-Password-Authentication" --users-body "$(cat path/to/users.json)" --upsert --email-results + auth0 users import -c "Username-Password-Authentication" --users-body "$(cat path/to/users.json)" --upsert --email-results --no-input + auth0 users import -c "Username-Password-Authentication" -b "$(cat path/to/users.json)" -u -r + auth0 users import -c "Username-Password-Authentication" -t "Basic Example" --upsert --email-results + auth0 users import -c "Username-Password-Authentication" -t "Basic Example" --upsert=false --email-results=false + auth0 users import -c "Username-Password-Authentication" -t "Basic Example" -u=false -r=false`, RunE: func(cmd *cobra.Command, args []string) error { - // Select from the available connection types - // Users API currently support database connections - options, optionsErr := cli.connectionPickerOptions() - if optionsErr != nil { - return optionsErr + // Users API currently only supports database connections. + dbConnectionOptions, err := cli.dbConnectionPickerOptions() + if err != nil { + return err } - if err := userConnection.Select(cmd, &inputs.Connection, options, nil); err != nil { + if err := userConnection.Select(cmd, &inputs.Connection, dbConnectionOptions, nil); err != nil { return err } - // Get Connection ID - conn, connErr := cli.api.Connection.ReadByName(inputs.Connection) - if connErr != nil { - return fmt.Errorf("Connection does not exist: %w", connErr) + connection, err := cli.api.Connection.ReadByName(inputs.Connection) + if err != nil { + return fmt.Errorf("failed to find connection with name %q: %w", inputs.Connection, err) } - inputs.ConnectionID = *conn.ID - - // Present user with template options - if templateErr := userImportTemplate.Select(cmd, &inputs.Template, userImportOptions.labels(), nil); templateErr != nil { - return templateErr - } + inputs.ConnectionID = connection.GetID() - editorErr := userImportTemplateBody.OpenEditor( - cmd, - &inputs.TemplateBody, - userImportOptions.getValue(inputs.Template), - inputs.Template+".*.json", - cli.userImportEditorHint, - ) - if editorErr != nil { - return fmt.Errorf("Failed to capture input from the editor: %w", editorErr) - } + if inputs.UsersBody == "" { + err := userImportTemplate.Select(cmd, &inputs.Template, userImportOptions.labels(), nil) + if err != nil { + return err + } - var confirmed bool - if confirmedErr := prompt.AskBool("Do you want to import these user(s)?", &confirmed, true); confirmedErr != nil { - return fmt.Errorf("Failed to capture prompt input: %w", confirmedErr) + if err := userImportBody.OpenEditor( + cmd, + &inputs.UsersBody, + userImportOptions.getValue(inputs.Template), + inputs.Template+".*.json", + cli.userImportEditorHint, + ); err != nil { + return fmt.Errorf("failed to capture input from the editor: %w", err) + } } - if !confirmed { - return nil + if canPrompt(cmd) { + var confirmed bool + if err := prompt.AskBool("Do you want to import these user(s)?", &confirmed, true); err != nil { + return fmt.Errorf("failed to capture prompt input: %w", err) + } + if !confirmed { + return nil + } } - // Convert json array to map - jsonstr := inputs.TemplateBody - var jsonmap []map[string]interface{} - jsonErr := json.Unmarshal([]byte(jsonstr), &jsonmap) - if jsonErr != nil { - return fmt.Errorf("Invalid JSON input: %w", jsonErr) + var usersBody []map[string]interface{} + if err := json.Unmarshal([]byte(inputs.UsersBody), &usersBody); err != nil { + return fmt.Errorf("invalid JSON input: %w", err) } - err := ansi.Waiting(func() error { - return cli.api.Jobs.ImportUsers(&management.Job{ - ConnectionID: &inputs.ConnectionID, - Users: jsonmap, - Upsert: &inputs.Upsert, - SendCompletionEmail: &inputs.SendCompletionEmail, - }) - }) - if err != nil { + if err := ansi.Waiting(func() error { + return cli.api.Jobs.ImportUsers( + &management.Job{ + ConnectionID: &inputs.ConnectionID, + Users: usersBody, + Upsert: &inputs.Upsert, + SendCompletionEmail: &inputs.SendCompletionEmail, + }, + ) + }); err != nil { return err } cli.renderer.Heading("starting user import job...") - fmt.Println(jsonstr) + cli.renderer.JSONResult(usersBody) + cli.renderer.Newline() + cli.renderer.Newline() if inputs.SendCompletionEmail { cli.renderer.Infof("Results of your user import job will be sent to your email.") @@ -630,8 +636,10 @@ The file size limit for a bulk import is 500KB. You will need to start multiple userConnection.RegisterString(cmd, &inputs.Connection, "") userImportTemplate.RegisterString(cmd, &inputs.Template, "") + userImportBody.RegisterString(cmd, &inputs.UsersBody, "") userEmailResults.RegisterBool(cmd, &inputs.SendCompletionEmail, true) userImportUpsert.RegisterBool(cmd, &inputs.Upsert, false) + cmd.MarkFlagsMutuallyExclusive("template", "users-body") return cmd } @@ -643,17 +651,15 @@ func formatUserDetailsPath(id string) string { return fmt.Sprintf("users/%s", id) } -func (c *cli) connectionPickerOptions() ([]string, error) { - var res []string - - list, err := c.api.Connection.List() +func (c *cli) dbConnectionPickerOptions() ([]string, error) { + list, err := c.api.Connection.List(management.Parameter("strategy", management.ConnectionStrategyAuth0)) if err != nil { return nil, err } + + var res []string for _, conn := range list.Connections { - if conn.GetStrategy() == "auth0" { - res = append(res, conn.GetName()) - } + res = append(res, conn.GetName()) } if len(res) == 0 { @@ -666,9 +672,8 @@ func (c *cli) connectionPickerOptions() ([]string, error) { func (c *cli) getUserConnection(users *management.User) []string { var res []string for _, i := range users.Identities { - res = append(res, fmt.Sprintf("%v", auth0.StringValue(i.Connection))) + res = append(res, fmt.Sprintf("%s", i.GetConnection())) } - return res } diff --git a/internal/cli/users_test.go b/internal/cli/users_test.go index 7ff91fa4d..10ecd5e3a 100644 --- a/internal/cli/users_test.go +++ b/internal/cli/users_test.go @@ -52,25 +52,6 @@ func TestConnectionsPickerOptions(t *testing.T) { assert.ErrorContains(t, err, "There are currently no database connections.") }, }, - { - name: "no database connections", - connections: []*management.Connection{ - { - Name: auth0.String("some-name-1"), - Strategy: auth0.String("foo"), - }, - { - Name: auth0.String("some-name-2"), - Strategy: auth0.String("foo"), - }, - }, - assertOutput: func(t testing.TB, options []string) { - t.Fail() - }, - assertError: func(t testing.TB, err error) { - assert.ErrorContains(t, err, "There are currently no database connections.") - }, - }, { name: "API error", apiError: errors.New("error"), @@ -98,7 +79,7 @@ func TestConnectionsPickerOptions(t *testing.T) { api: &auth0.API{Connection: connectionAPI}, } - options, err := cli.connectionPickerOptions() + options, err := cli.dbConnectionPickerOptions() if err != nil { test.assertError(t, err) diff --git a/test/integration/users-test-cases.yaml b/test/integration/users-test-cases.yaml index 4d663ee33..ba560d38a 100644 --- a/test/integration/users-test-cases.yaml +++ b/test/integration/users-test-cases.yaml @@ -107,3 +107,13 @@ tests: stderr: contains: - "Open the following URL in a browser: https://manage.auth0.com/dashboard/" + + 016 - users import: + command: auth0 users import -c "Username-Password-Authentication" -b "[]" -r=false --no-input + exit-code: 0 + stdout: + contains: + - "[]" + stderr: + contains: + - "starting user import job..."