@@ -3522,7 +3522,7 @@ def __init__(self):
35223522 group .add_argument ("--path-style-addressing" , dest = "blob_storage_path_style_addressing" , metavar = "<0|1>" ,
35233523 choices = ["0" , "1" ], help = "Use BLOB storage path style addressing" )
35243524
3525- # We disable the cluster init check so people can use '--set' before the cluster is initiaslised . See MB-66986.
3525+ # We disable the cluster init check so people can use '--set' before the cluster is initialised . See MB-66986.
35263526 @rest_initialiser (cluster_init_check = False , version_check = True )
35273527 def execute (self , opts ):
35283528 if opts .set :
@@ -6995,17 +6995,20 @@ def __init__(self):
69956995 self .subparser = self .parser .add_subparsers (help = 'Sub command help' , dest = 'sub_cmd' , metavar = '<subcommand>' )
69966996 self .settings_cmd = BackupServiceSettings (self .subparser )
69976997 self .repository_cmd = BackupServiceRepository (self .subparser )
6998+ self .repo_list_cmd = BackupServiceRepoList (self .subparser )
69986999 self .plan_cmd = BackupServicePlan (self .subparser )
69997000 self .nodeThreads_cmd = BackupServiceNodeThreadsMap (self .subparser )
70007001
70017002 def execute (self , opts ):
7002- if opts .sub_cmd is None or opts .sub_cmd not in ['settings' , 'repository' , 'plan' , 'node-threads' ]:
7003- _exit_if_errors (['<subcommand> must be one of [settings, repository, plan, node-threads]' ])
7003+ if opts .sub_cmd is None or opts .sub_cmd not in ['settings' , 'repository' , 'repo-list' , ' plan' , 'node-threads' ]:
7004+ _exit_if_errors (['<subcommand> must be one of [settings, repository, repo-list, plan, node-threads]' ])
70047005
70057006 if opts .sub_cmd == 'settings' :
70067007 self .settings_cmd .execute (opts )
70077008 elif opts .sub_cmd == 'repository' :
70087009 self .repository_cmd .execute (opts )
7010+ elif opts .sub_cmd == 'repo-list' :
7011+ self .repo_list_cmd .execute (opts )
70097012 elif opts .sub_cmd == 'plan' :
70107013 self .plan_cmd .execute (opts )
70117014 elif opts .sub_cmd == 'node-threads' :
@@ -7078,6 +7081,167 @@ def get_description():
70787081 return 'Manage backup service settings'
70797082
70807083
7084+ def human_friendly_print_repository (repository ):
7085+ """Print the repository in a human friendly format
7086+
7087+ Args:
7088+ repository (obj): The backup repository information
7089+ """
7090+
7091+ print (f'ID: { repository ["id" ]} ' )
7092+ print (f'State: { repository ["state" ]} ' )
7093+ print (f'Healthy: { (not ("health" in repository and not repository ["health" ]["healthy" ]))!s} ' )
7094+ print (f'Archive: { repository ["archive" ]} ' )
7095+ print (f'Repository: { repository ["repo" ]} ' )
7096+ if 'bucket' in repository :
7097+ print (f'Bucket: { repository ["bucket" ]["name" ]} ' )
7098+ if 'plan_name' in repository and repository ['plan_name' ] != "" :
7099+ print (f'plan: { repository ["plan_name" ]} ' )
7100+ print (f'Creation time: { repository ["creation_time" ]} ' )
7101+
7102+ if 'scheduled' in repository and repository ['scheduled' ]:
7103+ print ()
7104+ human_friendly_print_repository_scheduled_tasks (repository ['scheduled' ])
7105+
7106+ one_off = repository ['running_one_off' ] if 'running_one_off' in repository else None
7107+ running_scheduled = repository ['running_tasks' ] if 'running_tasks' in repository else None
7108+ if one_off or running_scheduled :
7109+ print ()
7110+ human_friendly_print_running_tasks (one_off , running_scheduled )
7111+
7112+
7113+ def human_friendly_print_running_tasks (one_off , scheduled ):
7114+ """Prints the running task summary in a human friendly way
7115+
7116+ Args:
7117+ one_off (map<str, task object>): Running one off tasks
7118+ scheduled (map<str, task object>): Running scheduled tasks
7119+ """
7120+ all_vals = []
7121+ name_pad = 5
7122+ if one_off :
7123+ for name in one_off :
7124+ if len (name ) > name_pad :
7125+ name_pad = len (name )
7126+ all_vals += one_off .values ()
7127+
7128+ if scheduled :
7129+ for name in scheduled :
7130+ if len (name ) > name_pad :
7131+ name_pad = len (name )
7132+ all_vals += scheduled .values ()
7133+
7134+ name_pad += 1
7135+
7136+ header = f'{ "Name" :<{name_pad }} | Task type | Status | Start'
7137+ print (header )
7138+ print ('-' * (len (header ) + 5 ))
7139+ for task in all_vals :
7140+ print (f'{ task ["name" ]:<{name_pad }} | { task ["type" ].title ():<10} | { task ["status" ]:<8} | { task ["start" ]} ' )
7141+
7142+
7143+ def human_friendly_print_repository_scheduled_tasks (scheduled ):
7144+ """Print the scheduled task in a tabular format"""
7145+ name_pad = 5
7146+ for name in scheduled :
7147+ if len (name ) > name_pad :
7148+ name_pad = len (name )
7149+ name_pad += 1
7150+
7151+ header = f'{ "Name" :<{name_pad }} | Task type | Next run'
7152+ print ('Scheduled tasks:' )
7153+ print (header )
7154+ print ('-' * (len (header ) + 5 ))
7155+
7156+ for task in scheduled .values ():
7157+ print (f'{ task ["name" ]:<{name_pad }} | { task ["task_type" ].title ():<10} | { task ["next_run" ]} ' )
7158+
7159+
7160+ def human_friendly_print_repositories (repositories_map ):
7161+ """This will print the repositories in a tabular format
7162+
7163+ Args:
7164+ repository_map (map<state (str), repository (list of objects)>)
7165+ """
7166+ repository_count = 0
7167+ id_pad = 5
7168+ plan_pad = 7
7169+ for repositories in repositories_map .values ():
7170+ for repository in repositories :
7171+ repository_count += 1
7172+ if id_pad < len (repository ['id' ]):
7173+ id_pad = len (repository ['id' ])
7174+ if 'plan_name' in repository and plan_pad < len (repository ['plan_name' ]):
7175+ plan_pad = len (repository ['plan_name' ])
7176+
7177+ if repository_count == 0 :
7178+ print ('No repositories found' )
7179+ return
7180+
7181+ # Get an extra space between the information and the column separator
7182+ plan_pad += 1
7183+ id_pad += 1
7184+
7185+ # build header
7186+ header = f'{ "ID" :<{id_pad }} | { "State" :<9} | { "Plan" :<{plan_pad }} | Healthy | Repository'
7187+ print (header )
7188+ print ('-' * len (header ))
7189+
7190+ # print repository summary
7191+ for _ , repositories in sorted (repositories_map .items ()):
7192+ for repository in repositories :
7193+ healthy = not ('health' in repository and not repository ['health' ]['healthy' ])
7194+ # archived and imported repositories may not have plans so we have to replace the empty string with N/A
7195+ plan_name = 'N/A'
7196+ if 'plan_name' in repository and len (repository ['plan_name' ]) != 0 :
7197+ plan_name = repository ['plan_name' ]
7198+
7199+ print (f"{ repository ['id' ]:<{id_pad }} | { repository ['state' ]:<9} | { plan_name :<{plan_pad }} | "
7200+ f" { healthy !s:<7} | { repository ['repo' ]} " )
7201+
7202+
7203+ class BackupServiceRepoList :
7204+ """List the backup repositories.
7205+
7206+ If a repository state is given only repositories in that state will be listed. This command supports listing both in
7207+ json and human friendly format.
7208+ """
7209+
7210+ def __init__ (self , subparser ):
7211+ """setup the parser"""
7212+ self .rest = None
7213+ repository_parser = subparser .add_parser ('repo-list' , help = 'List backup repositories' , add_help = False ,
7214+ allow_abbrev = False )
7215+
7216+ repository_parser .add_argument (
7217+ '--state' , metavar = '<state>' , choices = ['active' , 'archived' , 'imported' ],
7218+ help = 'The repository state. Used to retrieve only repositories in a specific state. If not provided all '
7219+ 'repositories will be returned regardless of their state (optional)' )
7220+
7221+ @rest_initialiser (version_check = True , enterprise_check = True , cluster_init_check = True )
7222+ def execute (self , opts ):
7223+ """Run the backup-service repo-list subcommand"""
7224+ states = ['active' , 'archived' , 'imported' ] if opts .state is None else [opts .state ]
7225+ results = {}
7226+ for get_state in states :
7227+ repositories , errors = self .rest .get_backup_service_repositories (state = get_state )
7228+ _exit_if_errors (errors )
7229+ results [get_state ] = repositories
7230+
7231+ if opts .output == 'json' :
7232+ print (json .dumps (results , indent = 2 ))
7233+ else :
7234+ human_friendly_print_repositories (results )
7235+
7236+ @staticmethod
7237+ def get_man_page_name ():
7238+ return get_doc_page_name ("couchbase-cli-backup-service-repo-list" )
7239+
7240+ @staticmethod
7241+ def get_description ():
7242+ return 'List backup service repositories'
7243+
7244+
70817245class BackupServiceRepository :
70827246 """This command manages backup services repositories.
70837247
@@ -7417,7 +7581,7 @@ def human_friendly_print_repositories(repositories_map):
74177581 print ('No repositories found' )
74187582 return
74197583
7420- # Get an extra space between the the information and the column separator
7584+ # Get an extra space between the information and the column separator
74217585 plan_pad += 1
74227586 id_pad += 1
74237587
0 commit comments