@@ -617,12 +617,14 @@ class BackendCommandArgumentParser:
617617
618618 def __init__ (self , backend , from_date = False , to_date = False , offset = False ,
619619 basic_auth = False , token_auth = False , archive = False ,
620- aliases = None , blacklist = False , ssl_verify = False ):
620+ aliases = None , blacklist = False , ssl_verify = False ,
621+ secrets_manager = False ):
621622 self ._from_date = from_date
622623 self ._to_date = to_date
623624 self ._archive = archive
624625 self ._backend = backend
625626 self ._ssl_verify = ssl_verify
627+ self ._secrets_manager = secrets_manager
626628
627629 self .aliases = aliases or {}
628630 self .parser = argparse .ArgumentParser ()
@@ -673,6 +675,9 @@ def __init__(self, backend, from_date=False, to_date=False, offset=False,
673675 group .add_argument ('--no-ssl-verify' , dest = 'ssl_verify' , action = 'store_false' ,
674676 help = "disable SSL verification" )
675677
678+ if secrets_manager :
679+ self ._set_secrets_manager_arguments ()
680+
676681 self ._set_output_arguments ()
677682
678683 def parse (self , * args ):
@@ -749,6 +754,39 @@ def _set_output_arguments(self):
749754 group .add_argument ('--json-line' , dest = 'json_line' , action = 'store_true' ,
750755 help = "produce a JSON line for each output item" )
751756
757+ def _set_secrets_manager_arguments (self ):
758+ """Activate secret manager arguments parsing"""
759+
760+ group = self .parser .add_argument_group ('secrets manager arguments' )
761+ group .add_argument ('--secrets-manager' , dest = 'secrets_manager' ,
762+ choices = ['bitwarden' , 'hashicorp' ],
763+ help = "Secrets manager service to use for credential retrieval" )
764+ group .add_argument ('--item-name' , dest = 'item_name' ,
765+ help = "Name of the item in the secrets manager" )
766+
767+ bw_group = self .parser .add_argument_group ('bitwarden arguments' )
768+ bw_group .add_argument ('--bw-client-id' , dest = 'bw_client_id' ,
769+ default = os .environ .get ('PERCEVAL_BW_CLIENT_ID' ),
770+ help = "Bitwarden API client ID (env: PERCEVAL_BW_CLIENT_ID)" )
771+ bw_group .add_argument ('--bw-client-secret' , dest = 'bw_client_secret' ,
772+ default = os .environ .get ('PERCEVAL_BW_CLIENT_SECRET' ),
773+ help = "Bitwarden API client secret (env: PERCEVAL_BW_CLIENT_SECRET)" )
774+ bw_group .add_argument ('--bw-master-password' , dest = 'bw_master_password' ,
775+ default = os .environ .get ('PERCEVAL_BW_MASTER_PASSWORD' ),
776+ help = "Bitwarden master password (env: PERCEVAL_BW_MASTER_PASSWORD)" )
777+
778+ hc_group = self .parser .add_argument_group ('hashicorp vault arguments' )
779+ hc_group .add_argument ('--vault-url' , dest = 'vault_url' ,
780+ default = os .environ .get ('PERCEVAL_VAULT_URL' ),
781+ help = "HashiCorp Vault URL (env: PERCEVAL_VAULT_URL)" )
782+ hc_group .add_argument ('--vault-token' , dest = 'vault_token' ,
783+ default = os .environ .get ('PERCEVAL_VAULT_TOKEN' ),
784+ help = "HashiCorp Vault authentication token (env: PERCEVAL_VAULT_TOKEN)" )
785+ hc_group .add_argument ('--vault-certificate' , dest = 'vault_certificate' ,
786+ default = os .environ .get ('PERCEVAL_VAULT_CERTIFICATE' ),
787+ help = "Path to CA certificate for HashiCorp Vault TLS verification "
788+ "(env: PERCEVAL_VAULT_CERTIFICATE)" )
789+
752790
753791class BackendCommand :
754792 """Abstract class to run backends from the command line.
@@ -822,8 +860,76 @@ def run(self):
822860 logger .exception (f"Error!: { e } " , exc_info = self .debug )
823861
824862 def _pre_init (self ):
825- """Override to execute before backend is initialized."""
826- pass
863+ """Override to execute before backend is initialized.
864+
865+ This method handles fetching credentials from a secrets manager
866+ and injecting them into backend arguments.
867+ """
868+ if not (hasattr (self .parsed_args , 'secrets_manager' ) and
869+ self .parsed_args .secrets_manager ):
870+ return
871+
872+ if not getattr (self .parsed_args , 'item_name' , None ):
873+ raise ValueError ("--item-name is required when --secrets-manager is specified." )
874+
875+ logging .debug ("Processing credentials with %s" , self .parsed_args .secrets_manager )
876+
877+ try :
878+ manager = self ._build_manager ()
879+ field_names = ['user' , 'password' , 'api_token' , 'email' , 'access_token' , 'user_id' ]
880+
881+ credentials = manager .resolve_credentials (
882+ secret_name = self .parsed_args .item_name ,
883+ field_names = field_names ,
884+ )
885+
886+ # Post-process: GitHub backend expects api_token as a list
887+ if 'api_token' in credentials :
888+ credentials ['api_token' ] = [credentials ['api_token' ]]
889+
890+ # Inject resolved credentials into parsed_args
891+ for param_name , value in credentials .items ():
892+ setattr (self .parsed_args , param_name , value )
893+ logger .info ('Using %s from secrets manager' , param_name )
894+
895+ except ImportError :
896+ logging .warning ('Credential management module not found. Using command line credentials.' )
897+ except Exception as e :
898+ raise RuntimeError ('Error retrieving credentials from secret manager: %s' % str (e )) from e
899+
900+ def _build_manager (self ):
901+ """Build and return a credential manager instance from parsed CLI args.
902+
903+ :returns: A credential manager instance
904+ :rtype: CredentialManager
905+ """
906+ manager_type = self .parsed_args .secrets_manager
907+
908+ if manager_type == 'bitwarden' :
909+ bw_client_id = getattr (self .parsed_args , 'bw_client_id' , None )
910+ bw_client_secret = getattr (self .parsed_args , 'bw_client_secret' , None )
911+ bw_master_password = getattr (self .parsed_args , 'bw_master_password' , None )
912+
913+ if not all ([bw_client_id , bw_client_secret , bw_master_password ]):
914+ raise ValueError (
915+ 'Bitwarden requires --bw-client-id, --bw-client-secret, and --bw-master-password'
916+ )
917+
918+ from grimoirelab_toolkit .credential_manager .bw_manager import BitwardenManager
919+ return BitwardenManager (bw_client_id , bw_client_secret , bw_master_password )
920+
921+ elif manager_type == 'hashicorp' :
922+ vault_url = getattr (self .parsed_args , 'vault_url' , None )
923+ vault_token = getattr (self .parsed_args , 'vault_token' , None )
924+ vault_certificate = getattr (self .parsed_args , 'vault_certificate' , None )
925+
926+ if not all ([vault_url , vault_token ]):
927+ raise ValueError ('HashiCorp Vault requires --vault-url and --vault-token' )
928+
929+ from grimoirelab_toolkit .credential_manager .hc_manager import HashicorpManager
930+ return HashicorpManager (vault_url , vault_token , vault_certificate )
931+
932+ raise ValueError (f"Unsupported secrets manager: '{ manager_type } '" )
827933
828934 def _post_init (self ):
829935 """Override to execute after backend is initialized."""
0 commit comments