11# Copyright Stacklet, Inc.
22# SPDX-License-Identifier: Apache-2.0
33
4- import json
5- import os
64import pathlib
75from urllib .parse import urlsplit , urlunsplit
86
97import click
108import jwt
119import requests
1210
13- from stacklet . client . platform .cognito import CognitoUserManager
14- from stacklet . client . platform .commands import commands
15- from stacklet . client . platform . config import StackletConfig
16- from stacklet . client . platform .context import StackletContext
17- from stacklet . client . platform .formatter import Formatter
18- from stacklet . client . platform . utils import click_group_entry , default_options
11+ from .cognito import CognitoUserManager
12+ from .commands import commands
13+ from . config import DEFAULT_CONFIG_FILE , DEFAULT_OUTPUT_FORMAT , StackletConfig
14+ from .context import StackletContext
15+ from .formatter import Formatter
16+ from . utils import setup_logging
1917
2018
2119@click .group ()
22- @default_options ()
20+ @click .option (
21+ "--config" ,
22+ type = click .Path (path_type = pathlib .Path , dir_okay = False ),
23+ default = DEFAULT_CONFIG_FILE ,
24+ envvar = "STACKLET_CONFIG" ,
25+ show_envvar = True ,
26+ help = "Configuration file" ,
27+ )
28+ @click .option (
29+ "--output" ,
30+ type = click .Choice (list (Formatter .registry .keys ()), case_sensitive = False ),
31+ default = DEFAULT_OUTPUT_FORMAT ,
32+ envvar = "STACKLET_OUTPUT" ,
33+ show_envvar = True ,
34+ help = "Output type" ,
35+ )
36+ @click .option (
37+ "-v" ,
38+ count = True ,
39+ default = 0 ,
40+ help = "Verbosity level, increase verbosity by appending v, e.g. -vvv" ,
41+ )
2342@click .pass_context
24- def cli (* args , ** kwargs ):
43+ def cli (
44+ ctx ,
45+ config ,
46+ output ,
47+ v ,
48+ ):
2549 """
2650 Stacklet CLI
2751
@@ -30,6 +54,21 @@ def cli(*args, **kwargs):
3054
3155 $ stacklet-admin configure
3256
57+ or specify the configuration parameters directly:
58+
59+ \b
60+ $ stacklet-admin \\
61+ configure \\
62+ --api $stacklet_api \\
63+ --cognito-client-id $cognito_client_id \\
64+ --cognito-user-pool-id $cognito_user_pool_id \\
65+ --region $region
66+ \b
67+
68+ In alternative, autoconfiguration can be used by specifying the deployment URL:
69+
70+ $ stacklet-admin auto-configure --url https://console.myorg.stacklet.io
71+
3372 If this is your first time using Stacklet, create a user:
3473
3574 $ stacklet-admin user add
@@ -48,24 +87,15 @@ def cli(*args, **kwargs):
4887
4988 To specify a different configuration file or different API endpoint/Cognito Configuration:
5089
51- \b
52- $ stacklet-admin \\
53- --api $stacklet_api \\
54- --cognito-client-id $cognito_client_id \\
55- --cognito-user-pool-id $cognito_user_pool_id \\
56- --region $region \\
57- login
58-
59- or:
60-
6190 \b
6291 $ stacklet-admin \\
6392 --config $config_file_location \\
6493 login
94+ \b
6595
6696 Specify different output types:
6797
68- $ stacklet-admin account --output json list --provider AWS
98+ $ stacklet-admin --output json account list --provider AWS
6999
70100 Stacklet queries default to 20 entries per page. To use pagiantion:
71101
@@ -76,22 +106,30 @@ def cli(*args, **kwargs):
76106 --before $before_token \\
77107 --after $after_token \\
78108 list
109+ \b
79110 """
80- click_group_entry (* args , ** kwargs )
111+ setup_logging (v )
112+ ctx .obj = StackletContext (config_file = config , output_format = output )
81113
82114
83115@cli .command (short_help = "Configure stacklet-admin cli" )
84- @click .option ("--api" , prompt = "Stacklet API endpoint" )
85- @click .option ("--region" , prompt = "Cognito Region" )
86- @click .option ("--cognito-client-id" , prompt = "Cognito User Pool Client ID" )
87- @click .option ("--cognito-user-pool-id" , prompt = "Cognito User Pool ID" )
88- @click .option ("--idp-id" , prompt = "(SSO) IDP ID" , default = "" )
89- @click .option ("--auth-url" , prompt = "(SSO) Auth Url" , default = "" )
90- @click .option ("--cubejs" , prompt = "Stacklet cube.js endpoint" , default = "" )
91- @click .option ("--location" , prompt = "Config File Location" , default = "~/.stacklet/config.json" ) # noqa
92- @click .pass_context
116+ @click .option ("--api" , prompt = "Stacklet API endpoint" , help = "Stacklet API endpoint" )
117+ @click .option ("--region" , prompt = "Cognito Region" , help = "Cognito Region" )
118+ @click .option (
119+ "--cognito-client-id" , prompt = "Cognito User Pool Client ID" , help = "Cognito User Pool Client ID"
120+ )
121+ @click .option ("--cognito-user-pool-id" , prompt = "Cognito User Pool ID" , help = "Cognito User Pool ID" )
122+ @click .option ("--cubejs" , prompt = "Stacklet cube.js endpoint" , help = "Stacklet cube.js endpoint" )
123+ @click .option ("--idp-id" , prompt = "(SSO) IDP ID" , default = "" , help = "(SSO) IDP ID" )
124+ @click .option ("--auth-url" , prompt = "(SSO) Auth Url" , default = "" , help = "(SSO) Auth Url" )
125+ @click .option (
126+ "--location" ,
127+ type = click .Path (path_type = pathlib .Path , dir_okay = False ),
128+ default = DEFAULT_CONFIG_FILE ,
129+ prompt = "Config File Location" ,
130+ help = "Config File Location" ,
131+ )
93132def configure (
94- ctx ,
95133 api ,
96134 region ,
97135 cognito_client_id ,
@@ -104,24 +142,18 @@ def configure(
104142 """
105143 Interactively save a Stacklet Config file
106144 """
107- config = {
108- "api" : api ,
109- "region" : region ,
110- "cognito_client_id" : cognito_client_id ,
111- "cognito_user_pool_id" : cognito_user_pool_id ,
112- "idp_id" : idp_id ,
113- "auth_url" : auth_url ,
114- "cubejs" : cubejs ,
115- }
116-
117- StackletConfig .validate (config )
118-
119- if not os .path .exists (location ):
120- dirs = location .rsplit ("/" , 1 )[0 ]
121- os .makedirs (os .path .expanduser (dirs ), exist_ok = True )
122-
123- with open (os .path .expanduser (location ), "w+" ) as f :
124- f .write (json .dumps (config ))
145+ config = StackletConfig .from_dict (
146+ {
147+ "api" : api ,
148+ "region" : region ,
149+ "cognito_client_id" : cognito_client_id ,
150+ "cognito_user_pool_id" : cognito_user_pool_id ,
151+ "idp_id" : idp_id ,
152+ "auth_url" : auth_url ,
153+ "cubejs" : cubejs ,
154+ }
155+ )
156+ config .to_file (location )
125157 click .echo (f"Saved config to { location } " )
126158
127159
@@ -140,7 +172,7 @@ def configure(
140172)
141173@click .option (
142174 "--location" ,
143- default = "~/.stacklet/config.json" ,
175+ default = str ( DEFAULT_CONFIG_FILE ) ,
144176 type = click .Path (path_type = pathlib .Path , dir_okay = False ),
145177 show_default = True ,
146178)
@@ -253,34 +285,28 @@ def auto_configure(url, prefix, idp, location):
253285 except KeyError as err :
254286 raise click .ClickException (f"The configuration details are missing a required key: { err } " )
255287
256- config_file = location .expanduser ()
257- if not config_file .exists ():
258- config_file .parent .mkdir (parents = True , exist_ok = True )
259-
260- config_file .write_text (json .dumps (formatted_config , indent = 4 , sort_keys = True ))
261-
262- click .echo (f"Saved config to { config_file } " )
288+ config = StackletConfig .from_dict (formatted_config )
289+ config .to_file (location )
290+ click .echo (f"Saved config to { location } " )
263291
264292
265293@cli .command ()
266- @click .pass_context
267- def show (ctx ):
294+ @click .pass_obj
295+ def show (obj ):
268296 """
269297 Show your config
270298 """
271- context = StackletContext (ctx .obj ["config" ], ctx .obj ["raw_config" ])
272- fmt = Formatter .registry .get (ctx .obj ["output" ])()
273- if os .path .exists (os .path .expanduser (StackletContext .DEFAULT_ID )):
274- with open (os .path .expanduser (StackletContext .DEFAULT_ID ), "r" ) as f :
275- id_details = jwt .decode (f .read (), options = {"verify_signature" : False })
299+ fmt = obj .formatter ()
300+ if id_token := obj .credentials .id_token ():
301+ id_details = jwt .decode (id_token , options = {"verify_signature" : False })
276302 click .echo (fmt (id_details ))
277303 click .echo ()
278- click .echo (fmt (context .config .to_json ()))
304+ click .echo (fmt (obj .config .to_dict ()))
279305
280306
281307@cli .command (short_help = "Login to Stacklet" )
282- @click .option ("--username" , required = False )
283- @click .option ("--password" , hide_input = True , required = False )
308+ @click .option ("--username" , help = "Login username" , required = False )
309+ @click .option ("--password" , help = "Login password" , hide_input = True , required = False )
284310@click .pass_context
285311def login (ctx , username , password ):
286312 """
@@ -295,38 +321,37 @@ def login(ctx, username, password):
295321
296322 If password is not passed in, your password will be prompted
297323 """
298- context = StackletContext (ctx .obj ["config" ], ctx .obj ["raw_config" ])
324+ context = ctx .obj
325+ config = context .config
326+ can_sso_login = bool (config .auth_url )
299327 # sso login
300- if context . can_sso_login () and not any ([username , password ]):
301- from stacklet . client . platform .vendored .auth import BrowserAuthenticator
328+ if can_sso_login and not any ([username , password ]):
329+ from .vendored .auth import BrowserAuthenticator
302330
303331 BrowserAuthenticator (
304- authority_url = context . config .auth_url ,
305- client_id = context . config .cognito_client_id ,
306- idp_id = context . config .idp_id ,
332+ authority_url = config .auth_url ,
333+ client_id = config .cognito_client_id ,
334+ idp_id = config .idp_id ,
307335 )()
308336 return
309- elif not context . can_sso_login () and not any ([username , password ]):
337+ elif not can_sso_login and not any ([username , password ]):
310338 click .echo (
311339 "To login with SSO ensure that your configuration includes "
312340 + "auth_url, and cognito_client_id values."
313341 )
314- raise Exception ( )
342+ ctx . exit ( 1 )
315343
316344 # handle login with username/password
317345 if not username :
318346 username = click .prompt ("Username" )
319347 if not password :
320348 password = click .prompt ("Password" , hide_input = True )
321349 manager = CognitoUserManager .from_context (context )
322- res = manager .login (
350+ id_token , access_token = manager .login (
323351 user = username ,
324352 password = password ,
325353 )
326- if not os .path .exists (os .path .dirname (os .path .expanduser (StackletContext .DEFAULT_CREDENTIALS ))):
327- os .makedirs (os .path .dirname (os .path .expanduser (StackletContext .DEFAULT_CREDENTIALS )))
328- with open (os .path .expanduser (StackletContext .DEFAULT_CREDENTIALS ), "w+" ) as f : # noqa
329- f .write (res )
354+ context .credentials .write (id_token , access_token )
330355
331356
332357for c in commands :
0 commit comments