Skip to content

Commit 7657b56

Browse files
committed
MB-70103 Add backup-service repo-list command
Duplicates the functionality of backup-service repository --list which will be deprecated. Change-Id: I563ad4fe6fc7f09a0bf4bb9fad653569970cf84c Reviewed-on: https://review.couchbase.org/c/couchbase-cli/+/238409 Reviewed-by: Safian Ali <safian.ali@couchbase.com> Tested-by: Build Bot <build@couchbase.com>
1 parent 0a5726f commit 7657b56

10 files changed

Lines changed: 473 additions & 11 deletions

cbmgr.py

Lines changed: 168 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
70817245
class 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

docs/modules/cli/pages/_partials/cbcli/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
** xref:cli:cbcli/couchbase-cli-backup-service.adoc[backup-service]
55
** xref:cli:cbcli/couchbase-cli-backup-service-nodes-threads-map.adoc[backup-service nodes-threads-map]
66
** xref:cli:cbcli/couchbase-cli-backup-service-plan.adoc[backup-service plan]
7+
** xref:cli:cbcli/couchbase-cli-backup-service-repo-list.adoc[backup-service repo-list]
78
** xref:cli:cbcli/couchbase-cli-backup-service-repository.adoc[backup-service repository]
89
** xref:cli:cbcli/couchbase-cli-backup-service-settings.adoc[backup-service settings]
910
** xref:cli:cbcli/couchbase-cli-bucket-compact.adoc[bucket-compact]

docs/modules/cli/pages/cbcli/couchbase-cli-backup-service-nodes-threads-map.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
= couchbase-cli-backup-service-nodes-threads-map(1)
2-
ifndef::doctype-manpage[:doctitle: backup-service-nodes-threads-map]
2+
ifndef::doctype-manpage[:doctitle: backup-service nodes-threads-map]
33

44
ifdef::doctype-manpage[]
55
== NAME

docs/modules/cli/pages/cbcli/couchbase-cli-backup-service-plan.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
= couchbase-cli-backup-service-plan(1)
2-
ifndef::doctype-manpage[:doctitle: backup-service-plan]
2+
ifndef::doctype-manpage[:doctitle: backup-service plan]
33

44
ifdef::doctype-manpage[]
55
== NAME
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
= couchbase-cli-backup-service-repo-list(1)
2+
:description: List backup service repositories
3+
ifndef::doctype-manpage[:doctitle: backup-service repo-list]
4+
5+
ifdef::doctype-manpage[]
6+
== NAME
7+
8+
couchbase-cli-backup-service-repo-list -
9+
endif::[]
10+
List backup service repositories
11+
12+
== SYNOPSIS
13+
14+
[verse]
15+
_couchbase-cli backup-service_ [--cluster <url>] [--username <user>]
16+
[--password <password>] [--client-cert <path>] [--client-cert-password <password>]
17+
[--client-key <path>] [--client-key-password <password>] _repo-list_
18+
[--state <state>]
19+
20+
== DESCRIPTION
21+
22+
Lists the backup repositories. If a repository state is given, only repositories
23+
in that state will be listed. This command supports listing repositories in both
24+
JSON and human-friendly format.
25+
26+
== OPTIONS
27+
28+
--state <state>::
29+
Filters the repositories by state. Valid states are 'active', 'archived', or
30+
'imported'. If not specified, repositories in all states will be listed.
31+
32+
include::{partialsdir}/cbcli/part-common-options.adoc[]
33+
34+
include::{partialsdir}/cbcli/part-host-formats.adoc[]
35+
36+
include::{partialsdir}/cbcli/part-certificate-authentication.adoc[]
37+
38+
== EXAMPLES
39+
40+
To list all backup repositories in the cluster, run the following command.
41+
42+
----
43+
$ couchbase-cli backup-service -c 127.0.0.1:8091 -u Administrator -p password \
44+
repo-list
45+
46+
ID | State | Plan | Healthy | Repository
47+
----------------------------------------------------------
48+
weekly-all | active | _weekly | True | a8059549-7fc3-401a-8fb8-008d1e20f1b0
49+
old-data | archived | _daily | True | d6ccec04-6f03-4599-94c5-b95ac10a4f80
50+
test-data-set | imported | N/A | True | provider
51+
----
52+
53+
To list only active repositories, use the `--state` flag. Paused repos are also considered
54+
'active'.
55+
56+
----
57+
$ couchbase-cli backup-service -c 127.0.0.1:8091 -u Administrator -p password \
58+
repo-list --state active
59+
60+
ID | State | Plan | Healthy | Repository
61+
------------------------------------------------------------
62+
repo1 | active | _daily_backups | False | 7326081f-db10-49d1-9a55-fe80f676cde9
63+
repo2 | paused | _daily_backups | False | 179a36cc-2ad9-4360-a080-42ddc98e53c7
64+
----
65+
66+
To get the output in JSON format, use the `--output json` flag before the
67+
subcommand.
68+
69+
----
70+
$ couchbase-cli backup-service -c 127.0.0.1:8091 -u Administrator -p password \
71+
--output json repo-list --state active
72+
{
73+
"active": [
74+
{
75+
"id": "repo",
76+
"plan_name": "_daily_backups",
77+
"state": "active",
78+
"archive": "/path/to/archive",
79+
"repo": "179a36cc-2ad9-4360-a080-42ddc98e53c7",
80+
"scheduled": {
81+
"backup_hourly": {
82+
"name": "backup_hourly",
83+
"task_type": "BACKUP",
84+
"next_run": "2026-01-09T11:00:47Z"
85+
},
86+
"merge_every_6_hours": {
87+
"name": "merge_every_6_hours",
88+
"task_type": "MERGE",
89+
"next_run": "2026-01-09T15:30:46Z"
90+
},
91+
"merge_week": {
92+
"name": "merge_week",
93+
"task_type": "MERGE",
94+
"next_run": "2026-01-11T23:40:00Z"
95+
}
96+
},
97+
"version": 1,
98+
"health": {
99+
"healthy": true
100+
},
101+
"creation_time": "2025-12-19T14:15:02.29496Z",
102+
"update_time": "2026-01-09T09:32:58.514109Z"
103+
},
104+
]
105+
}
106+
----
107+
108+
== ENVIRONMENT AND CONFIGURATION VARIABLES
109+
110+
include::{partialsdir}/cbcli/part-common-env.adoc[]
111+
112+
== SEE ALSO
113+
114+
man:couchbase-cli-backup-service[1]
115+
116+
include::{partialsdir}/cbcli/part-footer.adoc[]
117+

docs/modules/cli/pages/cbcli/couchbase-cli-backup-service-repository.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ To retrieve a summary of all repositories run:
108108
----
109109
$ couchbase-cli backup-service -c 127.0.0.1:8091 -u Administrator -p password \
110110
repository --list
111-
ID | State | Profile | Healthy | Repository"
111+
ID | State | plan | Healthy | Repository"
112112
----------------------------------------------------------
113113
weekly-all | active | _weekly | True | a8059549-7fc3-401a-8fb8-008d1e20f1b0
114114
old-data | archived | _daily | True | d6ccec04-6f03-4599-94c5-b95ac10a4f80
@@ -120,7 +120,7 @@ details for the repository use the JSON output as can be seen below:
120120
121121
122122
123-
To retrieve just the information for one repository used instead the `--get` action flag as
123+
To retrieve just the information for one repository use the `--get` action flag as
124124
illustrated below.
125125
126126
----

docs/modules/cli/pages/cbcli/couchbase-cli-backup-service-settings.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
= couchbase-cli-backup-service-settings(1)
2-
ifndef::doctype-manpage[:doctitle: backup-service-settings]
2+
ifndef::doctype-manpage[:doctitle: backup-service settings]
33

44
ifdef::doctype-manpage[]
55
== NAME
@@ -11,9 +11,9 @@ Manage the backup service settings
1111
== SYNOPSIS
1212

1313
[verse]
14-
_couchbase-cli backup-service [--cluster <url>] [--username <user>]
14+
_couchbase-cli backup-service_ [--cluster <url>] [--username <user>]
1515
[--password <password>] [--client-cert <path>] [--client-cert-password <password>]
16-
[--client-key <path>] [--client-key-password <password>] settings [--get]
16+
[--client-key <path>] [--client-key-password <password>] _settings_ [--get]
1717
[--set] [--history-rotation-period <days>] [--history-rotation-size <mebibytes>]
1818

1919
== DESCRIPTION

docs/modules/cli/pages/cbcli/couchbase-cli-backup-service.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ man:couchbase-cli-backup-service-repository[1]::
3434
man:couchbase-cli-backup-service-plan[1]::
3535
Manage backup plans.
3636

37+
man:couchbase-cli-backup-service-nodes-threads-map[1]::
38+
Manage the backup service nodes threads map.
39+
40+
man:couchbase-cli-backup-service-repo-list[1]::
41+
List backup service repositories.
42+
3743
include::{partialsdir}/cbcli/part-host-formats.adoc[]
3844

3945
include::{partialsdir}/cbcli/part-certificate-authentication.adoc[]

0 commit comments

Comments
 (0)