Skip to content

Commit d322ce8

Browse files
KTanAug21Kathryn Anne S Tan
andauthored
Read the --org flag when running the flyctl tokens list command (#3799)
* initial * Update tokens list: 1. Read --org flag 2. Get the scope of tokens to list via detecting flags --a and --o passed: --- org scope: ONLY when the --org is passed --- app scope: 1. By default( no flags passed ) 2. The --a flag passed, regardless if --org passed or not 3. Throw an error if both the --app and --org flags are passed, and the resulting app name does not belong to the selected org, TODO: ADD TESTCASE PLEASE * Include scope flag Determine scope through flags provided Include --config into consideration for setting scope as "app" scope Throw an error if --scope provided string does not match "app" or "org" Print logs indicating scope(app or org), and name of app or org to get tokens for * trim trailing spaces * see if this fixes test error about triming triling whitespaces * Check if removing empty line removes fix trailing whitespace error * remove empty lines as indicated from precommit test result error * remove final trailing space please * remove the empty list_test.go * use gofmt on list.go * Add testcases for testing correct scope identification:app or org * Add final test for making sure app name is properly identified from --app flag, regardless if in a directory with a flytoml containing a diff app name * Replace hardcoded organization name in tokenstest with org from env * fix trailing whitespaces, gofmt the files please * add preflight top two lines to fix make test --------- Co-authored-by: Kathryn Anne S Tan <[email protected]>
1 parent faab36a commit d322ce8

File tree

2 files changed

+162
-1
lines changed

2 files changed

+162
-1
lines changed

internal/command/tokens/list.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ func newList() *cobra.Command {
3333
Name: "scope",
3434
Shorthand: "s",
3535
Description: "either 'app' or 'org'",
36-
Default: "app",
36+
Default: "",
3737
},
38+
flag.Org(),
3839
)
3940

4041
return cmd
@@ -45,19 +46,48 @@ func runList(ctx context.Context) (err error) {
4546
out := iostreams.FromContext(ctx).Out
4647
var rows [][]string
4748

49+
// Determine scope based on flags passed
4850
scope := flag.GetString(ctx, "scope")
51+
appFlag := flag.GetString(ctx, "app")
52+
configFlag := flag.GetString(ctx, "config")
53+
orgFlag := flag.GetString(ctx, "org")
54+
scope, err = determineScope(scope, appFlag, orgFlag, configFlag)
55+
if err != nil {
56+
return fmt.Errorf("failed retrieving scope: %w", err)
57+
}
58+
59+
// Apply scope to filter list of tokens to display
4960
switch scope {
5061
case "app":
5162
appName := appconfig.NameFromContext(ctx)
5263
if appName == "" {
5364
return command.ErrRequireAppName
5465
}
66+
// --org passed must match the selected app's org
67+
if orgFlag != "" {
68+
// Get app details, so we can identify its organization slug
69+
app, err := apiClient.GetAppCompact(ctx, appName)
70+
if err != nil {
71+
return fmt.Errorf("failed retrieving app %s: %w", appName, err)
72+
}
5573

74+
// Get organization details, so we can get its slug
75+
org, err := orgs.OrgFromEnvVarOrFirstArgOrSelect(ctx)
76+
if err != nil {
77+
return fmt.Errorf("failed retrieving org %w", err)
78+
}
79+
80+
// Throw an error if app's org slug does not match --org slug
81+
if app.Organization.Slug != org.Slug {
82+
return fmt.Errorf("failed to retrieve tokens, selected application \"%s\" does not belong to selected organization \"%s\"", appName, org.Slug)
83+
}
84+
}
5685
tokens, err := apiClient.GetAppLimitedAccessTokens(ctx, appName)
5786
if err != nil {
5887
return fmt.Errorf("failed retrieving tokens for app %s: %w", appName, err)
5988
}
6089

90+
fmt.Fprintln(out, "Tokens for app \""+appName+"\":")
6191
for _, token := range tokens {
6292
rows = append(rows, []string{token.Id, token.Name, token.User.Email, token.ExpiresAt.String()})
6393
}
@@ -68,10 +98,28 @@ func runList(ctx context.Context) (err error) {
6898
return fmt.Errorf("failed retrieving org %w", err)
6999
}
70100

101+
fmt.Fprintln(out, "Tokens for organization \""+org.Slug+"\":")
71102
for _, token := range org.LimitedAccessTokens.Nodes {
72103
rows = append(rows, []string{token.Id, token.Name, token.User.Email, token.ExpiresAt.String()})
73104
}
74105
}
106+
75107
_ = render.Table(out, "", rows, "ID", "Name", "Created By", "Expires At")
76108
return nil
77109
}
110+
111+
func determineScope(scopeStr string, appFlagStr string, orgFlagStr string, configFlagStr string) (scope string, err error) {
112+
// app scope is given highest priority, as it is more granular than org, identified by --app|--config|--scope=app
113+
// org scope is only used when specified by --org|--scope=org withought any app scope indicator
114+
// If scope is neither app nor org, but provided by the user, throw an error; there are only two scopes: app|org
115+
// the default scope, when all else fails, is app scope
116+
if appFlagStr != "" || configFlagStr != "" || scopeStr == "app" {
117+
return "app", nil
118+
} else if orgFlagStr != "" || scopeStr == "org" {
119+
return "org", nil
120+
} else if scopeStr != "" && scopeStr != "app" && scopeStr != "org" {
121+
return "", fmt.Errorf("Please provide a valid scope: \"app\" or \"org\".")
122+
} else {
123+
return "app", nil
124+
}
125+
}

test/preflight/fly_tokens_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//go:build integration
2+
// +build integration
3+
4+
package preflight
5+
6+
import (
7+
"github.com/stretchr/testify/require"
8+
"github.com/superfly/flyctl/test/preflight/testlib"
9+
"os"
10+
"testing"
11+
)
12+
13+
// TODO: list of things to test
14+
// - App scope is properly determined
15+
// - Org scope is properly determined
16+
// - App identified by flag is prioritized over the fly.toml file, because it's more visible
17+
18+
func TestTokensListDeterminesScopeApp(t *testing.T) {
19+
// Get library from preflight test lib using env variables form .direnv/preflight
20+
f := testlib.NewTestEnvFromEnv(t)
21+
// No need to run this on alternate sizes as it only tests the generated config.
22+
if f.VMSize != "" {
23+
t.Skip()
24+
}
25+
26+
// Create fly.toml in current directory for the purpose of testing --config purposes
27+
appName := f.CreateRandomAppName()
28+
f.Fly(
29+
"launch --now --org %s --name %s --region %s --image nginx --internal-port 80 --ha=false",
30+
f.OrgSlug(), appName, f.PrimaryRegion(),
31+
)
32+
33+
// List of commands to verify scope app is selected for
34+
commandList := [9](string){
35+
// default, no flags
36+
"tokens list",
37+
// --app flag only
38+
"tokens list -a " + appName,
39+
// --config flag only
40+
"tokens list -c fly.toml",
41+
// --app flag, with --org flag
42+
"tokens list -a " + appName + " -o " + f.OrgSlug(),
43+
// --config flag, with --org flag
44+
"tokens list -c fly.toml -o " + f.OrgSlug(),
45+
// --scope is app
46+
"tokens list -s app -a " + appName,
47+
// --scope is app, with --org flag
48+
"tokens list -s app -a " + appName + " -o " + f.OrgSlug(),
49+
// --scope is org, but --config flag added
50+
"tokens list -s app -c fly.toml -o " + f.OrgSlug(),
51+
// --scope is org, but --app flag added
52+
"tokens list -s org " + f.OrgSlug() + " -a " + appName,
53+
}
54+
55+
for _, cmd_ := range commandList {
56+
console_out := f.Fly(cmd_).StdOutString()
57+
require.Contains(f, console_out, `Tokens for app`)
58+
}
59+
}
60+
61+
func TestTokensListDeterminesScopeOrg(t *testing.T) {
62+
// Get library from preflight test lib using env variables form .direnv/preflight
63+
f := testlib.NewTestEnvFromEnv(t)
64+
// No need to run this on alternate sizes as it only tests the generated config.
65+
if f.VMSize != "" {
66+
t.Skip()
67+
}
68+
69+
// List of commands to verify scope org is selected for
70+
commandList := [2](string){
71+
// --org flag alone
72+
"tokens list --org " + f.OrgSlug(),
73+
// --scope org
74+
"tokens list --scope org " + f.OrgSlug(),
75+
}
76+
77+
for _, cmd_ := range commandList {
78+
console_out := f.Fly(cmd_).StdOutString()
79+
require.Contains(f, console_out, `Tokens for organization`)
80+
}
81+
}
82+
83+
func TestTokensListPrioritizesAppFromFlagOverToml(t *testing.T) {
84+
// Get library from preflight test lib using env variables form .direnv/preflight
85+
f := testlib.NewTestEnvFromEnv(t)
86+
// No need to run this on alternate sizes as it only tests the generated config.
87+
if f.VMSize != "" {
88+
t.Skip()
89+
}
90+
91+
// Create fly.toml in current directory
92+
// Get this appName for verifying that the flag is prioritized over toml appname content
93+
appNameA := f.CreateRandomAppName()
94+
f.Fly(
95+
"launch --now --org %s --name %s --region %s --image nginx --internal-port 80 --ha=false",
96+
f.OrgSlug(), appNameA, f.PrimaryRegion(),
97+
)
98+
99+
// Delete this first app's toml file, so we can create a new one
100+
os.Remove(f.WorkDir() + "/fly.toml")
101+
appNameB := f.CreateRandomAppName()
102+
f.Fly(
103+
"launch --now --org %s --name %s --region %s --image nginx --internal-port 80 --ha=false",
104+
f.OrgSlug(), appNameB, f.PrimaryRegion(),
105+
)
106+
// By default use the app name found in the current dir should be used ( appNameB )
107+
console_out := f.Fly("tokens list").StdOutString()
108+
require.Contains(f, console_out, `Tokens for app "`+appNameB+`"`)
109+
110+
// But, the app name passed in --app should be prioritized over the toml appname ( appNameA )
111+
console_out = f.Fly("tokens list --app " + appNameA).StdOutString()
112+
require.Contains(f, console_out, `Tokens for app "`+appNameA+`"`)
113+
}

0 commit comments

Comments
 (0)