Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/auth0_actions_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ has_toc: false

List your existing actions. To create one, run: `auth0 actions create`.

Use '--schema' to see available query parameters.
Use '--query' to filter results via a JSON object (any API-supported parameter works immediately).

## Usage
```
auth0 actions list [flags]
Expand All @@ -20,6 +23,10 @@ auth0 actions list [flags]
auth0 actions ls --json
auth0 actions ls --json-compact
auth0 actions ls --csv
auth0 actions list --schema
auth0 actions list --schema --json
auth0 actions list --query '{"triggerId":"post-login"}'
auth0 actions list --query '{"deployed":"true"}' --json
```


Expand All @@ -29,6 +36,8 @@ auth0 actions list [flags]
--csv Output in csv format.
--json Output in json format.
--json-compact Output in compact json format.
-q, --query string Filter actions with a JSON object of query parameters (e.g. '{"triggerId":"post-login"}'). Any API-supported parameter works immediately. Run '--schema' to see documented parameters.
--schema Print the request payload schema for this command and exit. Use with --json or --json-compact for machine-readable output.
```


Expand Down
36 changes: 34 additions & 2 deletions internal/cli/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ var (
Help: "Action module to associate with the action, as comma-separated key=value pairs matching the API fields: module_id and module_version_id (both required, UUIDs). Can be passed multiple times to associate several modules.",
}

actionListQuery = Flag{
Name: "Query",
LongForm: "query",
ShortForm: "q",
Help: "Filter actions with a JSON object of query parameters (e.g. '{\"triggerId\":\"post-login\"}'). Any API-supported parameter works immediately. Run '--schema' to see documented parameters.",
}

actionTemplates = map[string]string{
"post-login": actionTemplatePostLogin,
"credentials-exchange": actionTemplateCredentialsExchange,
Expand Down Expand Up @@ -125,18 +132,41 @@ For more details: https://auth0.com/docs/api/management/v2`,
}

func listActionsCmd(cli *cli) *cobra.Command {
var inputs struct {
Schema bool
Query string
}

cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Args: cobra.NoArgs,
Short: "List your actions",
Long: "List your existing actions. To create one, run: `auth0 actions create`.",
Long: `List your existing actions. To create one, run: ` + "`auth0 actions create`" + `.

Use '--schema' to see available query parameters.
Use '--query' to filter results via a JSON object (any API-supported parameter works immediately).`,
Example: ` auth0 actions list
auth0 actions ls
auth0 actions ls --json
auth0 actions ls --json-compact
auth0 actions ls --csv`,
auth0 actions ls --csv
auth0 actions list --schema
auth0 actions list --schema --json
auth0 actions list --query '{"triggerId":"post-login"}'
auth0 actions list --query '{"deployed":"true"}' --json`,
RunE: func(cmd *cobra.Command, args []string) error {
if inputs.Schema {
return printOperationSchema(cli, "GET", "/actions/actions")
}

if inputs.Query != "" {
return runJSONQuery(cli, cmd, jsonQuerySpec{
Path: "actions/actions",
SchemaCmd: "auth0 actions list",
}, inputs.Query)
}

var list *management.ActionList

if err := ansi.Waiting(func() (err error) {
Expand All @@ -156,6 +186,8 @@ func listActionsCmd(cli *cli) *cobra.Command {
cmd.Flags().BoolVar(&cli.jsonCompact, "json-compact", false, "Output in compact json format.")
cmd.Flags().BoolVar(&cli.csv, "csv", false, "Output in csv format.")
cmd.MarkFlagsMutuallyExclusive("json", "json-compact", "csv")
schemaFlag.RegisterBool(cmd, &inputs.Schema, false)
actionListQuery.RegisterString(cmd, &inputs.Query, "")

return cmd
}
Expand Down
77 changes: 77 additions & 0 deletions internal/cli/actions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,83 @@ import (
"github.com/auth0/auth0-cli/internal/display"
)

func TestActionsListCmd(t *testing.T) {
t.Run("it lists actions using the default SDK path", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

actionAPI := mock.NewMockActionAPI(ctrl)
actionAPI.EXPECT().
List(context.Background(), gomock.Any()).
Return(&management.ActionList{
Actions: []*management.Action{
{
ID: auth0.String("action-1"),
Name: auth0.String("my-action"),
SupportedTriggers: []management.ActionTrigger{
{ID: auth0.String("post-login")},
},
},
},
}, nil)

stdout := &bytes.Buffer{}
cli := &cli{
renderer: &display.Renderer{
MessageWriter: io.Discard,
ResultWriter: stdout,
},
api: &auth0.API{Action: actionAPI},
}

cmd := listActionsCmd(cli)
err := cmd.Execute()

assert.NoError(t, err)
})

t.Run("it returns error when API fails", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

actionAPI := mock.NewMockActionAPI(ctrl)
actionAPI.EXPECT().
List(context.Background(), gomock.Any()).
Return(nil, errors.New("connection failed"))

stdout := &bytes.Buffer{}
cli := &cli{
renderer: &display.Renderer{
MessageWriter: io.Discard,
ResultWriter: stdout,
},
api: &auth0.API{Action: actionAPI},
}

cmd := listActionsCmd(cli)
err := cmd.Execute()

assert.EqualError(t, err, "failed to list actions: connection failed")
})

t.Run("it returns error for invalid --query JSON", func(t *testing.T) {
stdout := &bytes.Buffer{}
cli := &cli{
renderer: &display.Renderer{
MessageWriter: io.Discard,
ResultWriter: stdout,
},
api: &auth0.API{},
}

cmd := listActionsCmd(cli)
cmd.SetArgs([]string{"--query", "not-valid-json"})
err := cmd.Execute()

assert.ErrorContains(t, err, "invalid --query value")
})
}

func TestActionsDeployCmd(t *testing.T) {
t.Run("it successfully deploys an action", func(t *testing.T) {
actionID := "1221c74c-cfd6-40db-af13-7bc9bb1c38db"
Expand Down
69 changes: 69 additions & 0 deletions internal/cli/query_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package cli

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"

"github.com/spf13/cobra"

"github.com/auth0/auth0-cli/internal/ansi"
)

// jsonQuerySpec describes a list operation driven by a --query JSON payload.
type jsonQuerySpec struct {
Path string // API path segments (e.g. "actions/actions").
SchemaCmd string
}

// runJSONQuery executes a GET request against the Management API with query parameters parsed from queryJSON.
func runJSONQuery(cli *cli, cmd *cobra.Command, spec jsonQuerySpec, queryJSON string) error {
var queryParams map[string]interface{}
if err := json.Unmarshal([]byte(queryJSON), &queryParams); err != nil {
cli.renderer.Infof("Run '%s --schema' to see the expected query parameters.", spec.SchemaCmd)
return fmt.Errorf("invalid --query value: must be a JSON object: %w", err)
}

u, err := url.Parse(cli.api.HTTPClient.URI(strings.Split(spec.Path, "/")...))
if err != nil {
return fmt.Errorf("failed to parse URI: %w", err)
}
q := u.Query()
for key, val := range queryParams {
q.Set(key, fmt.Sprintf("%v", val))
}
u.RawQuery = q.Encode()

var response *http.Response
if err := ansi.Waiting(func() error {
request, err := cli.api.HTTPClient.NewRequest(cmd.Context(), http.MethodGet, u.String(), nil)
if err != nil {
return err
}
response, err = cli.api.HTTPClient.Do(request)
return err
}); err != nil {
return fmt.Errorf("failed to execute query: %w", err)
}
defer func() { _ = response.Body.Close() }()

rawJSON, err := io.ReadAll(response.Body)
if err != nil {
return err
}

if response.StatusCode >= http.StatusBadRequest {
return newAPIResponseError(response.StatusCode, response.Header, rawJSON)
}

var prettyJSON bytes.Buffer
if err := json.Indent(&prettyJSON, rawJSON, "", " "); err != nil {
return fmt.Errorf("failed to format response: %w", err)
}
cli.renderer.Output(ansi.ColorizeJSON(prettyJSON.String()))
return nil
}
Loading
Loading