@@ -46,6 +46,32 @@ def cmd_interface(args):
4646 # Early logging, in case the user requests debugging via env/CLI
4747 setup_early_logging (args )
4848
49+ if args .multi_profiles :
50+ if args .aws_profile or ("TOKENDITO_AWS_PROFILE" in os .environ ):
51+ logger .warning (
52+ "Multiple profiles have been specified so the AWS profile value "
53+ "will be overridden by each profile."
54+ )
55+
56+ skip_auth = False
57+ for profile in args .multi_profiles :
58+ args .user_config_profile = profile
59+ args .aws_profile = profile
60+
61+ process_args (args , skip_auth )
62+
63+ # Reset config singleton but retain auth
64+ auth = config .okta ["password" ]
65+ config .set_defaults ()
66+ config .okta ["password" ] = auth
67+
68+ skip_auth = True
69+ else :
70+ process_args (args , False )
71+
72+
73+ def process_args (args , skip_auth = False ):
74+ """Process the args and allow for skipping auth with multiple profiles."""
4975 # Set some required initial values
5076 process_options (args )
5177
@@ -78,7 +104,7 @@ def cmd_interface(args):
78104 )
79105
80106 # get authentication and authorization cookies from okta
81- okta .access_control (config )
107+ _ = skip_auth or okta .access_control (config )
82108
83109 if config .okta ["tile" ]:
84110 tile_label = ""
@@ -166,6 +192,13 @@ def parse_cli_args(args):
166192 default = config .user ["config_profile" ],
167193 help = "Tokendito configuration profile to use." ,
168194 )
195+ parser .add_argument (
196+ "--multi-profiles" ,
197+ action = "append" ,
198+ help = "Tokendito configuration profiles to use. Can be specified multiple times. "
199+ "Using this will override --profile and cause --aws-profile to be ignored and "
200+ "replaced with this value." ,
201+ )
169202 parser .add_argument (
170203 "--config-file" ,
171204 dest = "user_config_file" ,
@@ -669,17 +702,11 @@ def add_sensitive_value_to_be_masked(value, key=None):
669702 mask_items .append (value )
670703
671704
672- def process_ini_file (file , profile ):
673- """Process options from a ConfigParser ini file.
674-
675- :param file: filename
676- :param profile: profile to read
677- :return: Config object with configuration values
678- """
705+ def _read_ini (file , profile , default_section ):
679706 res = dict ()
680707 pattern = re .compile (r"^(.*?)_(.*)" )
681708
682- ini = configparser .RawConfigParser (default_section = config . user [ "config_profile" ] )
709+ ini = configparser .RawConfigParser (default_section = default_section )
683710 # Here, group(1) is the dictionary key, and group(2) the configuration element
684711 try :
685712 ini .read (file )
@@ -693,26 +720,33 @@ def process_ini_file(file, profile):
693720 except configparser .Error as err :
694721 logger .error (f"Could not load profile '{ profile } ': { str (err )} " )
695722 sys .exit (2 )
723+
724+ return res
725+
726+
727+ def process_ini_file (file , profile ):
728+ """Process options from a ConfigParser ini file.
729+
730+ :param file: filename
731+ :param profile: profile to read
732+ :return: Config object with configuration values
733+ """
734+ res = _read_ini (file , profile , config .user ["config_profile" ])
696735 logger .debug (f"Found ini directives: { res } " )
736+ if not res :
737+ return None
697738
698739 try :
699- config_ini = Config (** res )
700-
740+ return Config (** res )
701741 except (AttributeError , KeyError , ValueError ) as err :
702742 logger .error (
703743 f"The configuration file { file } in [{ profile } ] is incorrect: { err } "
704744 ". Please check your settings and try again."
705745 )
706746 sys .exit (1 )
707- return config_ini
708747
709748
710- def process_arguments (args ):
711- """Process command-line arguments.
712-
713- :param args: argparse object
714- :return: Config object with configuration values
715- """
749+ def _read_arguments (args ):
716750 res = dict ()
717751 pattern = re .compile (r"^(.*?)_(.*)" )
718752
@@ -726,18 +760,29 @@ def process_arguments(args):
726760 if val :
727761 res [match .group (1 )][match .group (2 )] = val
728762 add_sensitive_value_to_be_masked (val , match .group (2 ))
763+
764+ return res
765+
766+
767+ def process_arguments (args ):
768+ """Process command-line arguments.
769+
770+ :param args: argparse object
771+ :return: Config object with configuration values
772+ """
773+ res = _read_arguments (args )
729774 logger .debug (f"Found arguments: { res } " )
775+ if not res :
776+ return None
730777
731778 try :
732- config_args = Config (** res )
733-
779+ return Config (** res )
734780 except (AttributeError , KeyError , ValueError ) as err :
735781 logger .error (
736782 f"Command line arguments not correct: { err } "
737783 ". This should not happen, please contact the package maintainers."
738784 )
739785 sys .exit (1 )
740- return config_args
741786
742787
743788def process_environment (prefix = "tokendito" ):
@@ -759,21 +804,42 @@ def process_environment(prefix="tokendito"):
759804 add_sensitive_value_to_be_masked (val , match .group (3 ))
760805 logger .debug (f"Found environment variables: { res } " )
761806
762- try :
763- config_env = Config ( ** res )
807+ if not res :
808+ return None
764809
810+ try :
811+ return Config (** res )
765812 except (AttributeError , KeyError , ValueError ) as err :
766813 logger .error (
767814 f"The environment variables are incorrectly set: { err } "
768815 ". Please check your settings and try again."
769816 )
770817 sys .exit (1 )
771- return config_env
818+
819+
820+ def _build_interactive_config (details , skip_password ):
821+ res = dict (okta = dict ())
822+ # Copy the values set by get_interactive_config
823+ if "okta_tile" in details :
824+ res ["okta" ]["tile" ] = details ["okta_tile" ]
825+ if "okta_org" in details :
826+ res ["okta" ]["org" ] = details ["okta_org" ]
827+ if "okta_username" in details :
828+ res ["okta" ]["username" ] = details ["okta_username" ]
829+
830+ if ("password" not in config .okta or config .okta ["password" ] == "" ) and not skip_password :
831+ logger .debug ("No password set, will try to get one interactively" )
832+ res ["okta" ]["password" ] = get_secret_input ()
833+ add_sensitive_value_to_be_masked (res ["okta" ]["password" ])
834+
835+ logger .debug (f"Interactive configuration is: { res } " )
836+
837+ return Config (** res )
772838
773839
774840def process_interactive_input (config , skip_password = False ):
775841 """
776- Request input interactively interactively for elements that are not proesent .
842+ Request input interactively interactively for elements that are not present .
777843
778844 :param config: Config object with some values set.
779845 :param skip_password: Whether or not ask the user for a password.
@@ -782,7 +848,7 @@ def process_interactive_input(config, skip_password=False):
782848 # Return quickly if the user attempts to run in quiet (non-interactive) mode.
783849 if config .user ["quiet" ] is True :
784850 logger .debug (f"Skipping interactive config: quiet mode is { config .user ['quiet' ]} " )
785- return config
851+ return None
786852
787853 # Reuse interactive config. It will only request the portions needed.
788854 try :
@@ -795,25 +861,10 @@ def process_interactive_input(config, skip_password=False):
795861 logger .error (f"Interactive arguments are not correct: { err } " )
796862 sys .exit (1 )
797863
798- # Create a dict that can be passed to Config later
799- res = dict (okta = dict ())
800- # Copy the values set by get_interactive_config
801- if "okta_tile" in details :
802- res ["okta" ]["tile" ] = details ["okta_tile" ]
803- if "okta_org" in details :
804- res ["okta" ]["org" ] = details ["okta_org" ]
805- if "okta_username" in details :
806- res ["okta" ]["username" ] = details ["okta_username" ]
807-
808- if ("password" not in config .okta or config .okta ["password" ] == "" ) and not skip_password :
809- logger .debug ("No password set, will try to get one interactively" )
810- res ["okta" ]["password" ] = get_secret_input ()
811- add_sensitive_value_to_be_masked (res ["okta" ]["password" ])
864+ if not details :
865+ return None
812866
813- config_int = Config (** res )
814- logger .debug (f"Interactive configuration is: { config_int } " )
815- config .update (config_int )
816- return config_int
867+ return _build_interactive_config (details , skip_password )
817868
818869
819870def get_interactive_config (tile = None , org = None , username = "" ):
@@ -1190,23 +1241,25 @@ def process_options(args):
11901241 sys .exit (0 )
11911242
11921243 # 1: read ini file (if it exists)
1193- config_ini = Config ()
11941244 if not args .configure :
11951245 config_ini = process_ini_file (args .user_config_file , args .user_config_profile )
1246+ if config_ini :
1247+ config .update (config_ini )
11961248
11971249 # 2: override with ENV
11981250 config_env = process_environment ()
1251+ if config_env :
1252+ config .update (config_env )
11991253
12001254 # 3: override with args
12011255 config_args = process_arguments (args )
1202-
1203- config .update (config_ini )
1204- config .update (config_env )
1205- config .update (config_args )
1256+ if config_args :
1257+ config .update (config_args )
12061258
12071259 # 4: Get missing data from the user, if necessary
12081260 config_int = process_interactive_input (config , args .configure )
1209- config .update (config_int )
1261+ if config_int :
1262+ config .update (config_int )
12101263
12111264 sanitize_config_values (config )
12121265 logger .debug (f"Final configuration is { config } " )
0 commit comments