esp-rainmaker-cli 1.13.0__tar.gz → 1.14.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/PKG-INFO +15 -1
  2. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/esp_rainmaker_cli.egg-info/PKG-INFO +15 -1
  3. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rainmaker/rainmaker.py +150 -1
  4. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rainmaker/version.py +1 -1
  5. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/group.py +218 -2
  6. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/node.py +10 -2
  7. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/configmanager.py +1 -1
  8. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/session.py +148 -0
  9. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/LICENSE +0 -0
  10. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/README.md +0 -0
  11. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/esp_rainmaker_cli.egg-info/SOURCES.txt +0 -0
  12. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/esp_rainmaker_cli.egg-info/dependency_links.txt +0 -0
  13. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/esp_rainmaker_cli.egg-info/entry_points.txt +0 -0
  14. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/esp_rainmaker_cli.egg-info/requires.txt +0 -0
  15. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/esp_rainmaker_cli.egg-info/top_level.txt +0 -0
  16. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rainmaker/__init__.py +0 -0
  17. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/__init__.py +0 -0
  18. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/automation.py +0 -0
  19. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/browserlogin.py +0 -0
  20. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/cache.py +0 -0
  21. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/cmd_response.py +0 -0
  22. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/html/welcome.html +0 -0
  23. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/provision.py +0 -0
  24. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/stream.py +0 -0
  25. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/test.py +0 -0
  26. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/user.py +0 -0
  27. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/__init__.py +0 -0
  28. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/aws_credentials.py +0 -0
  29. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/cmd_response.py +0 -0
  30. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/constants.py +0 -0
  31. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/device.py +0 -0
  32. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/envval.py +0 -0
  33. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/exceptions.py +0 -0
  34. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/kvs_streaming.py +0 -0
  35. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/local_control.py +0 -0
  36. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/logger.py +0 -0
  37. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/node.py +0 -0
  38. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/node_cache.py +0 -0
  39. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/profile_manager.py +0 -0
  40. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/profile_utils.py +0 -0
  41. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/schedule_utils.py +0 -0
  42. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/serverconfig.py +0 -0
  43. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/service.py +0 -0
  44. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/session_store.py +0 -0
  45. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/simple_local_control.py +0 -0
  46. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_lib/user.py +0 -0
  47. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/__init__.py +0 -0
  48. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/__init__.py +0 -0
  49. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/discovery/__init__.py +0 -0
  50. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/discovery/mdns_discovery.py +0 -0
  51. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/prov/__init__.py +0 -0
  52. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/prov/wifi_ctrl.py +0 -0
  53. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/prov/wifi_prov.py +0 -0
  54. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/prov/wifi_scan.py +0 -0
  55. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/security/__init__.py +0 -0
  56. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/security/security.py +0 -0
  57. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/security/security0.py +0 -0
  58. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/security/security1.py +0 -0
  59. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/security/security2.py +0 -0
  60. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/security/srp6a.py +0 -0
  61. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/transport/__init__.py +0 -0
  62. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/transport/ble_cli.py +0 -0
  63. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/transport/transport.py +0 -0
  64. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/transport/transport_ble.py +0 -0
  65. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/transport/transport_console.py +0 -0
  66. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/transport/transport_http.py +0 -0
  67. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/utils/__init__.py +0 -0
  68. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/utils/convenience.py +0 -0
  69. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_claim/__init__.py +0 -0
  70. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_claim/claim.py +0 -0
  71. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_claim/claim_config.py +0 -0
  72. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/__init__.py +0 -0
  73. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/constants_pb2.py +0 -0
  74. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/esp_local_ctrl.py +0 -0
  75. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/esp_local_ctrl_pb2.py +0 -0
  76. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/esp_prov.py +0 -0
  77. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/esp_rainmaker_ctrl.py +0 -0
  78. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/esp_rmaker_prov_local_ctrl_pb2.py +0 -0
  79. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/integration.py +0 -0
  80. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/proto/__init__.py +0 -0
  81. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/proto/proto_lc.py +0 -0
  82. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/prov/__init__.py +0 -0
  83. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/prov/custom_prov.py +0 -0
  84. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/prov/wifi_ctrl.py +0 -0
  85. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/raw_config.py +0 -0
  86. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/raw_params.py +0 -0
  87. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/sec0_pb2.py +0 -0
  88. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/sec1_pb2.py +0 -0
  89. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/sec2_pb2.py +0 -0
  90. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/security/__init__.py +0 -0
  91. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/session_pb2.py +0 -0
  92. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/transport/__init__.py +0 -0
  93. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/utils/__init__.py +0 -0
  94. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/__init__.py +0 -0
  95. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/challenge_response.py +0 -0
  96. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/config/__init__.py +0 -0
  97. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/config/custom_cloud_config_pb2.py +0 -0
  98. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/config/esp_rmaker_chal_resp_pb2.py +0 -0
  99. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/config/esp_rmaker_claim_pb2.py +0 -0
  100. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/config/esp_rmaker_user_mapping_pb2.py +0 -0
  101. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/esp_rainmaker_prov.py +0 -0
  102. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/on_network_chal_resp.py +0 -0
  103. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/proto/__init__.py +0 -0
  104. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/__init__.py +0 -0
  105. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/python/__init__.py +0 -0
  106. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/python/constants_pb2.py +0 -0
  107. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/python/sec0_pb2.py +0 -0
  108. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/python/sec1_pb2.py +0 -0
  109. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/python/sec2_pb2.py +0 -0
  110. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/python/session_pb2.py +0 -0
  111. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/prov/__init__.py +0 -0
  112. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/prov/prov_util.py +0 -0
  113. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/prov/user_mapping.py +0 -0
  114. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/security/__init__.py +0 -0
  115. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/transport/__init__.py +0 -0
  116. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/utils/__init__.py +0 -0
  117. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/wifi_provisioning/__init__.py +0 -0
  118. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/__init__.py +0 -0
  119. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_config_pb2.py +0 -0
  120. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_constants_pb2.py +0 -0
  121. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_ctrl_pb2.py +0 -0
  122. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_scan_pb2.py +0 -0
  123. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/server_cert/__init__.py +0 -0
  124. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/server_cert/server_cert.pem +0 -0
  125. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/setup.cfg +0 -0
  126. {esp_rainmaker_cli-1.13.0 → esp_rainmaker_cli-1.14.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esp-rainmaker-cli
3
- Version: 1.13.0
3
+ Version: 1.14.0
4
4
  Summary: A python utility to perform host based claiming
5
5
  Home-page: https://github.com/espressif/esp-rainmaker-cli
6
6
  Author: Espressif Systems
@@ -122,6 +122,20 @@ Changelog
122
122
 
123
123
  All major changes to ESP RainMaker CLI will be documented in this file.
124
124
 
125
+ ## [1.14.0] - 24-Apr-2026
126
+ ### Added
127
+ - New `group sharing` subcommand for sharing device groups / Matter fabrics between users:
128
+ - `add`, `remove`, `list`, `list-requests`, `accept`, `decline`, `cancel` operations
129
+ - Supports primary/secondary roles, sub-roles (1-4), metadata, and ownership transfer (`--transfer`, `--new-role`)
130
+ - Parsed, human-readable output by default; pass `--raw` on any operation to get the underlying JSON response
131
+
132
+ ## [1.13.1] - 23-Apr-2026
133
+ ### Added
134
+ - Node sharing enhancements:
135
+ - Support for sharing as "primary"
136
+ - Transferring nodes
137
+ - Adding sub-roles
138
+
125
139
  ## [1.13.0] - 08-Apr-2026
126
140
  ### Added
127
141
  - New `stream` command for WebRTC video streaming from ESP RainMaker camera devices via Amazon Kinesis Video Streams (KVS) signaling:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esp-rainmaker-cli
3
- Version: 1.13.0
3
+ Version: 1.14.0
4
4
  Summary: A python utility to perform host based claiming
5
5
  Home-page: https://github.com/espressif/esp-rainmaker-cli
6
6
  Author: Espressif Systems
@@ -122,6 +122,20 @@ Changelog
122
122
 
123
123
  All major changes to ESP RainMaker CLI will be documented in this file.
124
124
 
125
+ ## [1.14.0] - 24-Apr-2026
126
+ ### Added
127
+ - New `group sharing` subcommand for sharing device groups / Matter fabrics between users:
128
+ - `add`, `remove`, `list`, `list-requests`, `accept`, `decline`, `cancel` operations
129
+ - Supports primary/secondary roles, sub-roles (1-4), metadata, and ownership transfer (`--transfer`, `--new-role`)
130
+ - Parsed, human-readable output by default; pass `--raw` on any operation to get the underlying JSON response
131
+
132
+ ## [1.13.1] - 23-Apr-2026
133
+ ### Added
134
+ - Node sharing enhancements:
135
+ - Support for sharing as "primary"
136
+ - Transferring nodes
137
+ - Adding sub-roles
138
+
125
139
  ## [1.13.0] - 08-Apr-2026
126
140
  ### Added
127
141
  - New `stream` command for WebRTC video streaming from ESP RainMaker camera devices via Amazon Kinesis Video Streams (KVS) signaling:
@@ -15,7 +15,12 @@ from rmaker_cmd.cmd_response import get_cmd_requests, create_cmd_request
15
15
  from rmaker_cmd.provision import provision
16
16
  from rmaker_cmd.test import test
17
17
  from rmaker_lib.logger import log
18
- from rmaker_cmd.group import group_add, group_remove, group_edit, group_list, group_show, group_add_nodes, group_remove_nodes, group_list_nodes
18
+ from rmaker_cmd.group import (group_add, group_remove, group_edit, group_list,
19
+ group_show, group_add_nodes, group_remove_nodes,
20
+ group_list_nodes, group_sharing_add,
21
+ group_sharing_remove, group_sharing_list,
22
+ group_sharing_list_requests, group_sharing_accept,
23
+ group_sharing_decline, group_sharing_cancel)
19
24
  from rmaker_cmd.automation import automation_add, automation_edit, automation_remove, automation_get
20
25
  from rmaker_cmd.cache import cache_manage
21
26
  from rmaker_cmd.stream import stream_video
@@ -668,6 +673,29 @@ def main():
668
673
  "format: <nodeid1>,<nodeid2>,...",
669
674
  required=True)
670
675
 
676
+ add_op_parser.add_argument('--sub_role',
677
+ type=int,
678
+ metavar='<sub_role>',
679
+ choices=range(1, 5),
680
+ help='Sub-role for the secondary user (1-4)',
681
+ default=None)
682
+
683
+ add_op_parser.add_argument('--primary',
684
+ action='store_true',
685
+ help='Set "primary":true in the sharing request')
686
+
687
+ add_op_parser.add_argument('--transfer',
688
+ action='store_true',
689
+ help='Transfer node ownership to the specified user')
690
+
691
+ add_op_parser.add_argument('--new_role',
692
+ type=str,
693
+ metavar='<new_role>',
694
+ choices=['primary', 'secondary'],
695
+ help='New role for the current user after transfer (primary/secondary).\n'
696
+ 'Only used with --transfer',
697
+ default=None)
698
+
671
699
  add_profile_argument(add_op_parser)
672
700
  add_op_parser.set_defaults(func=node_sharing_ops, parser=add_op_parser)
673
701
 
@@ -911,6 +939,127 @@ def main():
911
939
  add_profile_argument(group_list_nodes_parser)
912
940
  group_list_nodes_parser.set_defaults(func=group_list_nodes)
913
941
 
942
+ # group sharing
943
+ group_sharing_parser = group_subparsers.add_parser(
944
+ 'sharing',
945
+ help='Share groups / matter fabrics between users',
946
+ formatter_class=argparse.RawTextHelpFormatter,
947
+ description='Share groups or matter fabrics with other users.\n'
948
+ 'Primary users can share groups; secondary users can view '
949
+ 'their sharing or leave groups they have access to.')
950
+ group_sharing_parser.set_defaults(
951
+ func=lambda vars=None: group_sharing_parser.print_help())
952
+ group_sharing_subparsers = group_sharing_parser.add_subparsers(
953
+ dest='sharing_command', help='Group sharing operations')
954
+
955
+ # group sharing add
956
+ gs_add_parser = group_sharing_subparsers.add_parser(
957
+ 'add', help='Share group(s) with a user (or add self-sharing without --user)',
958
+ formatter_class=argparse.RawTextHelpFormatter)
959
+ gs_add_parser.add_argument('--groups', type=str, required=True,
960
+ metavar='<group_ids>',
961
+ help='Comma separated group ids (max 10)')
962
+ gs_add_parser.add_argument('--user', type=str, metavar='<user_name>',
963
+ help='User name (email) to share with. Optional.')
964
+ gs_add_parser.add_argument('--primary', action='store_true',
965
+ help='Share with primary access (default: secondary)')
966
+ gs_add_parser.add_argument('--sub-role', type=int, choices=range(1, 5),
967
+ metavar='<1-4>',
968
+ help='Custom sub role (1-4)')
969
+ gs_add_parser.add_argument('--metadata', type=str, metavar='<json>',
970
+ help='Custom metadata as a JSON string')
971
+ gs_add_parser.add_argument('--transfer', action='store_true',
972
+ help='Transfer ownership of the group(s) to the user')
973
+ gs_add_parser.add_argument('--new-role', type=str, choices=['secondary'],
974
+ help='Role to assign to self after transfer '
975
+ '(only valid with --transfer)')
976
+ gs_add_parser.add_argument('--raw', action='store_true',
977
+ help='Print the raw JSON response instead of parsed output')
978
+ add_profile_argument(gs_add_parser)
979
+ gs_add_parser.set_defaults(func=group_sharing_add)
980
+
981
+ # group sharing remove
982
+ gs_remove_parser = group_sharing_subparsers.add_parser(
983
+ 'remove', help='Remove group sharing with a user',
984
+ formatter_class=argparse.RawTextHelpFormatter)
985
+ gs_remove_parser.add_argument('--groups', type=str, required=True,
986
+ metavar='<group_ids>',
987
+ help='Comma separated group ids')
988
+ gs_remove_parser.add_argument('--user', type=str, required=True,
989
+ metavar='<user_name>',
990
+ help='User name (email) to unshare with')
991
+ gs_remove_parser.add_argument('--raw', action='store_true',
992
+ help='Print the raw JSON response instead of parsed output')
993
+ add_profile_argument(gs_remove_parser)
994
+ gs_remove_parser.set_defaults(func=group_sharing_remove)
995
+
996
+ # group sharing list
997
+ gs_list_parser = group_sharing_subparsers.add_parser(
998
+ 'list', help='Show sharing details for groups / matter fabrics',
999
+ formatter_class=argparse.RawTextHelpFormatter)
1000
+ gs_list_parser.add_argument('--group-id', type=str, metavar='<group_id>',
1001
+ help='Fetch sharing details for a single group')
1002
+ gs_list_parser.add_argument('--sub-groups', action='store_true',
1003
+ help='Include sharing details of sub-groups')
1004
+ gs_list_parser.add_argument('--parent-groups', action='store_true',
1005
+ help='Include sharing details of parent groups')
1006
+ gs_list_parser.add_argument('--metadata', action='store_true',
1007
+ help='Include metadata set during sharing')
1008
+ gs_list_parser.add_argument('--raw', action='store_true',
1009
+ help='Print the raw JSON response instead of parsed output')
1010
+ add_profile_argument(gs_list_parser)
1011
+ gs_list_parser.set_defaults(func=group_sharing_list)
1012
+
1013
+ # group sharing list-requests
1014
+ gs_list_req_parser = group_sharing_subparsers.add_parser(
1015
+ 'list-requests', help='List pending group sharing requests',
1016
+ formatter_class=argparse.RawTextHelpFormatter,
1017
+ description='List pending group sharing requests.\n'
1018
+ 'Use --primary-user to list requests raised by you.\n'
1019
+ 'Without the flag, lists requests you have received.')
1020
+ gs_list_req_parser.add_argument('--id', type=str, metavar='<request_id>',
1021
+ help='Fetch a specific request by id')
1022
+ gs_list_req_parser.add_argument('--primary-user', action='store_true',
1023
+ help='List requests raised by current user '
1024
+ '(default: requests received)')
1025
+ gs_list_req_parser.add_argument('--raw', action='store_true',
1026
+ help='Print the raw JSON response instead of parsed output')
1027
+ add_profile_argument(gs_list_req_parser)
1028
+ gs_list_req_parser.set_defaults(func=group_sharing_list_requests)
1029
+
1030
+ # group sharing accept
1031
+ gs_accept_parser = group_sharing_subparsers.add_parser(
1032
+ 'accept', help='Accept a pending group sharing request')
1033
+ gs_accept_parser.add_argument('--id', type=str, required=True,
1034
+ metavar='<request_id>',
1035
+ help='Id of the sharing request')
1036
+ gs_accept_parser.add_argument('--raw', action='store_true',
1037
+ help='Print the raw JSON response instead of parsed output')
1038
+ add_profile_argument(gs_accept_parser)
1039
+ gs_accept_parser.set_defaults(func=group_sharing_accept)
1040
+
1041
+ # group sharing decline
1042
+ gs_decline_parser = group_sharing_subparsers.add_parser(
1043
+ 'decline', help='Decline a pending group sharing request')
1044
+ gs_decline_parser.add_argument('--id', type=str, required=True,
1045
+ metavar='<request_id>',
1046
+ help='Id of the sharing request')
1047
+ gs_decline_parser.add_argument('--raw', action='store_true',
1048
+ help='Print the raw JSON response instead of parsed output')
1049
+ add_profile_argument(gs_decline_parser)
1050
+ gs_decline_parser.set_defaults(func=group_sharing_decline)
1051
+
1052
+ # group sharing cancel
1053
+ gs_cancel_parser = group_sharing_subparsers.add_parser(
1054
+ 'cancel', help='Cancel a pending group sharing request (primary side)')
1055
+ gs_cancel_parser.add_argument('--id', type=str, required=True,
1056
+ metavar='<request_id>',
1057
+ help='Id of the sharing request')
1058
+ gs_cancel_parser.add_argument('--raw', action='store_true',
1059
+ help='Print the raw JSON response instead of parsed output')
1060
+ add_profile_argument(gs_cancel_parser)
1061
+ gs_cancel_parser.set_defaults(func=group_sharing_cancel)
1062
+
914
1063
  # Automation Management
915
1064
  automation_parser = subparsers.add_parser('automations',
916
1065
  help='Manage automation triggers')
@@ -5,4 +5,4 @@
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
7
7
  # This file contains the version information for the ESP RainMaker CLI
8
- VERSION = "1.13.0"
8
+ VERSION = "1.14.0"
@@ -3,8 +3,17 @@
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
5
  from rmaker_lib.profile_utils import get_session_with_profile
6
+ import datetime
6
7
  import json
7
- from rmaker_cmd.node import _print_node_details
8
+ import sys
9
+ from rmaker_cmd.node import (
10
+ _get_description,
11
+ _get_request_expiration,
12
+ _get_request_id,
13
+ _get_status,
14
+ _print_api_error,
15
+ _print_node_details,
16
+ )
8
17
 
9
18
 
10
19
  def _handle_error(e, operation):
@@ -249,4 +258,211 @@ def group_list_nodes(vars=None):
249
258
  else:
250
259
  print(f"{idx}. {name} (ID: {group_id})")
251
260
  except Exception as e:
252
- _handle_error(e, "list nodes in group")
261
+ _handle_error(e, "list nodes in group")
262
+
263
+
264
+ def _split_groups(groups_arg):
265
+ return [g.strip() for g in groups_arg.split(',') if g.strip()]
266
+
267
+
268
+ def _print_group_sharing_request_details(resp, is_primary_user=False):
269
+ requests_in_resp = resp.get('sharing_requests') or []
270
+ if not requests_in_resp:
271
+ print("No pending requests")
272
+ return
273
+ request_exists = False
274
+ for request in requests_in_resp:
275
+ if request.get('request_status', '').lower() == 'declined':
276
+ continue
277
+ request_exists = True
278
+ print("\n{:<15}: {}".format('Request Id', request['request_id']))
279
+ print("{:<15}: {}".format('Group Id(s)', ','.join(request.get('group_ids', []))))
280
+ group_names = request.get('group_names') or []
281
+ if group_names:
282
+ print("{:<15}: {}".format('Group Name(s)', ','.join(group_names)))
283
+ if is_primary_user:
284
+ if 'user_name' in request and request['user_name']:
285
+ print("{:<15}: {}".format('Shared with', request['user_name']))
286
+ else:
287
+ if 'primary_user_name' in request and request['primary_user_name']:
288
+ print("{:<15}: {}".format('Shared by', request['primary_user_name']))
289
+ if 'user_role' in request and request['user_role']:
290
+ print("{:<15}: {}".format('Role', request['user_role']))
291
+ print(_get_request_expiration(request))
292
+
293
+ if not request_exists:
294
+ print("No pending requests")
295
+
296
+
297
+ def _print_group_sharing_details(resp):
298
+ try:
299
+ groups_in_resp = resp.get('group_sharing') or []
300
+ if not groups_in_resp:
301
+ print("No shared groups")
302
+ return
303
+ for group in groups_in_resp:
304
+ print("\nGroup Id: {}".format(group.get('group_id', '')))
305
+ users = group.get('users') or {}
306
+ primary_users = ','.join(users.get('primary') or [])
307
+ secondary_users = ','.join(users.get('secondary') or [])
308
+ print("{:<7}: {:<9}: {}".format('Users', 'Primary', primary_users), end='')
309
+ if secondary_users:
310
+ print("\n{:>18}: {}".format('Secondary', secondary_users), end='')
311
+ print()
312
+ except KeyError:
313
+ print("Error in displaying details...Please check API Json...Exiting...")
314
+ sys.exit(0)
315
+
316
+
317
+ def _emit(resp, raw, render):
318
+ if raw:
319
+ print(json.dumps(resp, indent=2))
320
+ return
321
+ if isinstance(resp, dict) and resp.get('status', '').lower() != 'success' and 'error_code' in resp:
322
+ _print_api_error(resp)
323
+ return
324
+ render(resp)
325
+
326
+
327
+ def group_sharing_add(vars=None):
328
+ try:
329
+ s = get_session_with_profile(vars or {})
330
+ groups = _split_groups(vars['groups'])
331
+ if not groups:
332
+ print("At least one group id must be provided in --groups")
333
+ return
334
+ user = vars.get('user')
335
+ primary = vars.get('primary') if vars.get('primary') else None
336
+ sub_role = vars.get('sub_role')
337
+ transfer = vars.get('transfer') if vars.get('transfer') else None
338
+ new_role = vars.get('new_role')
339
+ metadata = None
340
+ metadata_arg = vars.get('metadata')
341
+ if metadata_arg:
342
+ try:
343
+ metadata = json.loads(metadata_arg)
344
+ except Exception as e:
345
+ print("Invalid JSON for --metadata:", e)
346
+ return
347
+ print("Sharing group(s) with user")
348
+ resp = s.add_user_group_sharing(
349
+ groups=groups,
350
+ user_name=user,
351
+ primary=primary,
352
+ sub_role=sub_role,
353
+ metadata=metadata,
354
+ transfer=transfer,
355
+ new_role=new_role,
356
+ )
357
+
358
+ def render(r):
359
+ if r.get('status', '').lower() == 'success':
360
+ print("{:<11}: {}".format('Status', _get_status(r)))
361
+ print("{:<11}: {}".format('Description', _get_description(r)))
362
+ if 'request_id' in r:
363
+ print("{:<11}: {}".format('Request Id', _get_request_id(r)))
364
+
365
+ _emit(resp, vars.get('raw', False), render)
366
+ except Exception as e:
367
+ _handle_error(e, "add group sharing")
368
+
369
+
370
+ def group_sharing_remove(vars=None):
371
+ try:
372
+ s = get_session_with_profile(vars or {})
373
+ groups = _split_groups(vars['groups'])
374
+ if not groups:
375
+ print("At least one group id must be provided in --groups")
376
+ return
377
+ print("Removing group sharing")
378
+ resp = s.remove_user_group_sharing(
379
+ groups=','.join(groups),
380
+ user_name=vars['user'],
381
+ )
382
+
383
+ def render(r):
384
+ if r.get('status', '').lower() == 'success':
385
+ print("{}: {}".format(_get_status(r), _get_description(r)))
386
+
387
+ _emit(resp, vars.get('raw', False), render)
388
+ except Exception as e:
389
+ _handle_error(e, "remove group sharing")
390
+
391
+
392
+ def group_sharing_list(vars=None):
393
+ try:
394
+ s = get_session_with_profile(vars or {})
395
+ resp = s.get_user_group_sharing(
396
+ group_id=vars.get('group_id'),
397
+ sub_groups=vars.get('sub_groups', False),
398
+ parent_groups=vars.get('parent_groups', False),
399
+ metadata=vars.get('metadata', False),
400
+ )
401
+
402
+ def render(r):
403
+ print("Displaying group sharing details")
404
+ _print_group_sharing_details(r)
405
+
406
+ _emit(resp, vars.get('raw', False), render)
407
+ except Exception as e:
408
+ _handle_error(e, "list group sharing")
409
+
410
+
411
+ def group_sharing_list_requests(vars=None):
412
+ try:
413
+ s = get_session_with_profile(vars or {})
414
+ is_primary = vars.get('primary_user', False)
415
+ resp = s.get_user_group_sharing_requests(
416
+ request_id=vars.get('id'),
417
+ primary_user=is_primary,
418
+ )
419
+
420
+ def render(r):
421
+ print("Displaying pending group sharing requests")
422
+ print("Current (logged-in) user is set as {} user".format(
423
+ 'Primary' if is_primary else 'Secondary'))
424
+ _print_group_sharing_request_details(r, is_primary_user=is_primary)
425
+
426
+ _emit(resp, vars.get('raw', False), render)
427
+ except Exception as e:
428
+ _handle_error(e, "list group sharing requests")
429
+
430
+
431
+ def _respond_group_sharing(vars, accept):
432
+ action = 'accept' if accept else 'decline'
433
+ try:
434
+ s = get_session_with_profile(vars or {})
435
+ resp = s.respond_user_group_sharing_request(
436
+ request_id=vars['id'], accept=accept)
437
+
438
+ def render(r):
439
+ if r.get('status', '').lower() == 'success':
440
+ print("{:<11}: {}".format('Status', _get_status(r)))
441
+ print("{:<11}: {}".format('Description', _get_description(r)))
442
+
443
+ _emit(resp, vars.get('raw', False), render)
444
+ except Exception as e:
445
+ _handle_error(e, "{} group sharing request".format(action))
446
+
447
+
448
+ def group_sharing_accept(vars=None):
449
+ _respond_group_sharing(vars, accept=True)
450
+
451
+
452
+ def group_sharing_decline(vars=None):
453
+ _respond_group_sharing(vars, accept=False)
454
+
455
+
456
+ def group_sharing_cancel(vars=None):
457
+ try:
458
+ s = get_session_with_profile(vars or {})
459
+ print("Cancelling request")
460
+ resp = s.remove_user_group_sharing_request(request_id=vars['id'])
461
+
462
+ def render(r):
463
+ if r.get('status', '').lower() == 'success':
464
+ print("{}: {}".format(_get_status(r), _get_description(r)))
465
+
466
+ _emit(resp, vars.get('raw', False), render)
467
+ except Exception as e:
468
+ _handle_error(e, "cancel group sharing request")
@@ -897,7 +897,7 @@ def list_sharing_details(node_id=None, primary_user=False, request_id=None, list
897
897
 
898
898
  return node_json_resp
899
899
 
900
- def add_user_to_share_nodes(nodes=None, user=None, profile_override=None):
900
+ def add_user_to_share_nodes(nodes=None, user=None, sub_role=None, primary=False, transfer=False, new_role=None, profile_override=None):
901
901
  """
902
902
  Add user to share nodes
903
903
 
@@ -932,6 +932,14 @@ def add_user_to_share_nodes(nodes=None, user=None, profile_override=None):
932
932
  api_input = {}
933
933
  api_input['nodes'] = node_id_list
934
934
  api_input['user_name'] = user
935
+ if sub_role is not None:
936
+ api_input['sub_role'] = sub_role
937
+ if primary:
938
+ api_input['primary'] = True
939
+ if transfer:
940
+ api_input['transfer'] = True
941
+ if new_role is not None:
942
+ api_input['new_role'] = new_role
935
943
  log.debug("API data set: {}".format(api_input))
936
944
 
937
945
  # API with profile-aware session
@@ -1116,7 +1124,7 @@ def node_sharing_ops(vars=None):
1116
1124
  if action == 'add_user':
1117
1125
  # Share nodes with user
1118
1126
  print("Adding user to share node(s)")
1119
- node_json_resp = add_user_to_share_nodes(nodes=vars['nodes'], user=vars['user'], profile_override=profile_override)
1127
+ node_json_resp = add_user_to_share_nodes(nodes=vars['nodes'], user=vars['user'], sub_role=vars.get('sub_role'), primary=vars.get('primary', False), transfer=vars.get('transfer', False), new_role=vars.get('new_role'), profile_override=profile_override)
1120
1128
 
1121
1129
  # Print success response
1122
1130
  if 'status' in node_json_resp and node_json_resp['status'].lower() == 'success':
@@ -426,7 +426,7 @@ class Config:
426
426
  socket.setdefaulttimeout(10)
427
427
  log.info("Checking for supported version.")
428
428
  path = 'apiversions'
429
- request_url = self.get_host().split(serverconfig.VERSION)[0] + path
429
+ request_url = self.get_host().rsplit(serverconfig.VERSION, 1)[0] + path
430
430
  try:
431
431
  log.debug("Version check request url : " + request_url)
432
432
  response = requests.get(url=request_url, verify=CERT_FILE,
@@ -599,6 +599,154 @@ class Session:
599
599
  log.error(f'Failed to list nodes in group: {e}')
600
600
  raise
601
601
 
602
+ # Group Sharing API methods
603
+
604
+ def add_user_group_sharing(self, groups, user_name=None, primary=None,
605
+ sub_role=None, metadata=None, transfer=None,
606
+ new_role=None):
607
+ """Share one or more groups/matter fabrics with another user."""
608
+ path = 'user/node_group/sharing'
609
+ payload = {'groups': groups}
610
+ if user_name:
611
+ payload['user_name'] = user_name
612
+ if primary is not None:
613
+ payload['primary'] = primary
614
+ if sub_role is not None:
615
+ payload['sub_role'] = sub_role
616
+ if metadata is not None:
617
+ payload['metadata'] = metadata
618
+ if transfer is not None:
619
+ payload['transfer'] = transfer
620
+ if new_role:
621
+ payload['new_role'] = new_role
622
+ url = self.config.get_host() + path
623
+ log.debug(f"Add group sharing request url : {url}")
624
+ log.debug(f"Add group sharing request payload : {json.dumps(payload)}")
625
+ try:
626
+ response = requests.put(url=url, headers=self.request_header,
627
+ json=payload, verify=configmanager.CERT_FILE)
628
+ log.debug(f"Add group sharing response : {response.text}")
629
+ response.raise_for_status()
630
+ return response.json()
631
+ except Exception as e:
632
+ log.error(f'Failed to add group sharing: {e}')
633
+ raise
634
+
635
+ def remove_user_group_sharing(self, groups, user_name):
636
+ """Remove sharing of one or more groups/matter fabrics with a user."""
637
+ path = 'user/node_group/sharing'
638
+ params = {'groups': groups, 'user_name': user_name}
639
+ url = self.config.get_host() + path
640
+ log.debug(f"Remove group sharing request url : {url}")
641
+ log.debug(f"Remove group sharing request params : {params}")
642
+ try:
643
+ response = requests.delete(url=url, headers=self.request_header,
644
+ params=params, verify=configmanager.CERT_FILE)
645
+ log.debug(f"Remove group sharing response : {response.text}")
646
+ response.raise_for_status()
647
+ return response.json()
648
+ except Exception as e:
649
+ log.error(f'Failed to remove group sharing: {e}')
650
+ raise
651
+
652
+ def get_user_group_sharing(self, group_id=None, sub_groups=False,
653
+ parent_groups=False, metadata=False):
654
+ """Fetch group/fabric sharing details for the current user."""
655
+ path = 'user/node_group/sharing'
656
+ params = {}
657
+ if group_id:
658
+ params['group_id'] = group_id
659
+ if sub_groups:
660
+ params['sub_groups'] = 'true'
661
+ if parent_groups:
662
+ params['parent_groups'] = 'true'
663
+ if metadata:
664
+ params['metadata'] = 'true'
665
+ url = self.config.get_host() + path
666
+ log.debug(f"Get group sharing request url : {url}")
667
+ log.debug(f"Get group sharing request params : {params}")
668
+ try:
669
+ response = requests.get(url=url, headers=self.request_header,
670
+ params=params, verify=configmanager.CERT_FILE)
671
+ log.debug(f"Get group sharing response : {response.text}")
672
+ response.raise_for_status()
673
+ return response.json()
674
+ except Exception as e:
675
+ log.error(f'Failed to get group sharing: {e}')
676
+ raise
677
+
678
+ def get_user_group_sharing_requests(self, request_id=None, primary_user=None):
679
+ """Fetch pending group sharing requests (sent or received).
680
+
681
+ Pagination is handled internally: the server returns at most 10
682
+ records per call along with `next_request_id`/`next_user_name`
683
+ cursors, which are echoed back until the server stops returning
684
+ them. The consolidated list is returned to the caller.
685
+ """
686
+ path = 'user/node_group/sharing/requests'
687
+ params = {}
688
+ if request_id:
689
+ params['request_id'] = request_id
690
+ if primary_user is not None:
691
+ params['primary_user'] = 'true' if primary_user else 'false'
692
+ url = self.config.get_host() + path
693
+
694
+ all_requests = []
695
+ try:
696
+ while True:
697
+ log.debug(f"Get group sharing requests url : {url}")
698
+ log.debug(f"Get group sharing requests params : {params}")
699
+ response = requests.get(url=url, headers=self.request_header,
700
+ params=params, verify=configmanager.CERT_FILE)
701
+ log.debug(f"Get group sharing requests response : {response.text}")
702
+ response.raise_for_status()
703
+ data = response.json()
704
+ all_requests.extend(data.get('sharing_requests', []))
705
+ next_req = data.get('next_request_id')
706
+ if not next_req:
707
+ break
708
+ params['start_request_id'] = next_req
709
+ next_user = data.get('next_user_name')
710
+ if next_user:
711
+ params['start_user_name'] = next_user
712
+ return {'sharing_requests': all_requests, 'total': len(all_requests)}
713
+ except Exception as e:
714
+ log.error(f'Failed to get group sharing requests: {e}')
715
+ raise
716
+
717
+ def remove_user_group_sharing_request(self, request_id):
718
+ """Cancel a pending group sharing request (primary side)."""
719
+ path = 'user/node_group/sharing/requests'
720
+ params = {'request_id': request_id}
721
+ url = self.config.get_host() + path
722
+ log.debug(f"Cancel group sharing request url : {url}")
723
+ try:
724
+ response = requests.delete(url=url, headers=self.request_header,
725
+ params=params, verify=configmanager.CERT_FILE)
726
+ log.debug(f"Cancel group sharing request response : {response.text}")
727
+ response.raise_for_status()
728
+ return response.json()
729
+ except Exception as e:
730
+ log.error(f'Failed to cancel group sharing request: {e}')
731
+ raise
732
+
733
+ def respond_user_group_sharing_request(self, request_id, accept):
734
+ """Accept or decline a group sharing request."""
735
+ path = 'user/node_group/sharing/requests'
736
+ payload = {'accept': accept, 'request_id': request_id}
737
+ url = self.config.get_host() + path
738
+ log.debug(f"Respond group sharing request url : {url}")
739
+ log.debug(f"Respond group sharing request payload : {json.dumps(payload)}")
740
+ try:
741
+ response = requests.put(url=url, headers=self.request_header,
742
+ json=payload, verify=configmanager.CERT_FILE)
743
+ log.debug(f"Respond group sharing response : {response.text}")
744
+ response.raise_for_status()
745
+ return response.json()
746
+ except Exception as e:
747
+ log.error(f'Failed to respond to group sharing request: {e}')
748
+ raise
749
+
602
750
  # Automation Trigger API methods
603
751
 
604
752
  def create_automation(self, payload):