@@ -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,38 @@ 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 (env: PERCEVAL_VAULT_CERTIFICATE)" )
788+
752789
753790class BackendCommand :
754791 """Abstract class to run backends from the command line.
@@ -822,8 +859,76 @@ def run(self):
822859 logger .exception (f"Error!: { e } " , exc_info = self .debug )
823860
824861 def _pre_init (self ):
825- """Override to execute before backend is initialized."""
826- pass
862+ """Override to execute before backend is initialized.
863+
864+ This method handles fetching credentials from a secrets manager
865+ and injecting them into backend arguments.
866+ """
867+ if not (hasattr (self .parsed_args , 'secrets_manager' ) and
868+ self .parsed_args .secrets_manager ):
869+ return
870+
871+ if not getattr (self .parsed_args , 'item_name' , None ):
872+ raise ValueError ("--item-name is required when --secrets-manager is specified." )
873+
874+ logging .debug ("Processing credentials with %s" , self .parsed_args .secrets_manager )
875+
876+ try :
877+ manager = self ._build_manager ()
878+ field_names = ['user' , 'password' , 'api_token' , 'email' , 'access_token' , 'user_id' ]
879+
880+ credentials = manager .resolve_credentials (
881+ secret_name = self .parsed_args .item_name ,
882+ field_names = field_names ,
883+ )
884+
885+ # Post-process: GitHub backend expects api_token as a list
886+ if 'api_token' in credentials :
887+ credentials ['api_token' ] = [credentials ['api_token' ]]
888+
889+ # Inject resolved credentials into parsed_args
890+ for param_name , value in credentials .items ():
891+ setattr (self .parsed_args , param_name , value )
892+ logger .info ('Using %s from secrets manager' , param_name )
893+
894+ except ImportError :
895+ logging .warning ('Credential management module not found. Using command line credentials.' )
896+ except Exception as e :
897+ raise RuntimeError ('Error retrieving credentials from secret manager: %s' % str (e )) from e
898+
899+ def _build_manager (self ):
900+ """Build and return a credential manager instance from parsed CLI args.
901+
902+ :returns: A credential manager instance
903+ :rtype: CredentialManager
904+ """
905+ manager_type = self .parsed_args .secrets_manager
906+
907+ if manager_type == 'bitwarden' :
908+ bw_client_id = getattr (self .parsed_args , 'bw_client_id' , None )
909+ bw_client_secret = getattr (self .parsed_args , 'bw_client_secret' , None )
910+ bw_master_password = getattr (self .parsed_args , 'bw_master_password' , None )
911+
912+ if not all ([bw_client_id , bw_client_secret , bw_master_password ]):
913+ raise ValueError (
914+ 'Bitwarden requires --bw-client-id, --bw-client-secret, and --bw-master-password'
915+ )
916+
917+ from grimoirelab_toolkit .credential_manager .bw_manager import BitwardenManager
918+ return BitwardenManager (bw_client_id , bw_client_secret , bw_master_password )
919+
920+ elif manager_type == 'hashicorp' :
921+ vault_url = getattr (self .parsed_args , 'vault_url' , None )
922+ vault_token = getattr (self .parsed_args , 'vault_token' , None )
923+ vault_certificate = getattr (self .parsed_args , 'vault_certificate' , None )
924+
925+ if not all ([vault_url , vault_token ]):
926+ raise ValueError ('HashiCorp Vault requires --vault-url and --vault-token' )
927+
928+ from grimoirelab_toolkit .credential_manager .hc_manager import HashicorpManager
929+ return HashicorpManager (vault_url , vault_token , vault_certificate )
930+
931+ raise ValueError (f"Unsupported secrets manager: '{ manager_type } '" )
827932
828933 def _post_init (self ):
829934 """Override to execute after backend is initialized."""
0 commit comments