esp-rainmaker-cli 1.10.0__tar.gz → 1.11.1__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 (119) hide show
  1. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/PKG-INFO +8 -1
  2. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/esp_rainmaker_cli.egg-info/PKG-INFO +8 -1
  3. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rainmaker/rainmaker.py +102 -0
  4. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rainmaker/version.py +1 -1
  5. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_cmd/node.py +201 -1
  6. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_cmd/provision.py +20 -3
  7. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/node.py +178 -4
  8. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_claim/claim.py +15 -3
  9. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/challenge_response.py +18 -3
  10. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/esp_rainmaker_prov.py +4 -2
  11. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/on_network_chal_resp.py +19 -4
  12. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/LICENSE +0 -0
  13. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/README.md +0 -0
  14. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/esp_rainmaker_cli.egg-info/SOURCES.txt +0 -0
  15. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/esp_rainmaker_cli.egg-info/dependency_links.txt +0 -0
  16. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/esp_rainmaker_cli.egg-info/entry_points.txt +0 -0
  17. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/esp_rainmaker_cli.egg-info/requires.txt +0 -0
  18. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/esp_rainmaker_cli.egg-info/top_level.txt +0 -0
  19. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rainmaker/__init__.py +0 -0
  20. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_cmd/__init__.py +0 -0
  21. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_cmd/browserlogin.py +0 -0
  22. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_cmd/cmd_response.py +0 -0
  23. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_cmd/group.py +0 -0
  24. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_cmd/html/welcome.html +0 -0
  25. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_cmd/test.py +0 -0
  26. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_cmd/user.py +0 -0
  27. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/__init__.py +0 -0
  28. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/cmd_response.py +0 -0
  29. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/configmanager.py +0 -0
  30. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/constants.py +0 -0
  31. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/device.py +0 -0
  32. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/envval.py +0 -0
  33. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/exceptions.py +0 -0
  34. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/local_control.py +0 -0
  35. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/logger.py +0 -0
  36. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/profile_manager.py +0 -0
  37. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/profile_utils.py +0 -0
  38. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/schedule_utils.py +0 -0
  39. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/serverconfig.py +0 -0
  40. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/service.py +0 -0
  41. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/session.py +0 -0
  42. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/simple_local_control.py +0 -0
  43. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_lib/user.py +0 -0
  44. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/__init__.py +0 -0
  45. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/__init__.py +0 -0
  46. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/discovery/__init__.py +0 -0
  47. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/discovery/mdns_discovery.py +0 -0
  48. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/prov/__init__.py +0 -0
  49. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/prov/wifi_ctrl.py +0 -0
  50. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/prov/wifi_prov.py +0 -0
  51. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/prov/wifi_scan.py +0 -0
  52. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/security/__init__.py +0 -0
  53. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/security/security.py +0 -0
  54. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/security/security0.py +0 -0
  55. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/security/security1.py +0 -0
  56. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/security/security2.py +0 -0
  57. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/security/srp6a.py +0 -0
  58. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/transport/__init__.py +0 -0
  59. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/transport/ble_cli.py +0 -0
  60. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/transport/transport.py +0 -0
  61. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/transport/transport_ble.py +0 -0
  62. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/transport/transport_console.py +0 -0
  63. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/transport/transport_http.py +0 -0
  64. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/utils/__init__.py +0 -0
  65. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/common/utils/convenience.py +0 -0
  66. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_claim/__init__.py +0 -0
  67. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_claim/claim_config.py +0 -0
  68. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/__init__.py +0 -0
  69. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/constants_pb2.py +0 -0
  70. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/esp_local_ctrl.py +0 -0
  71. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/esp_local_ctrl_pb2.py +0 -0
  72. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/esp_prov.py +0 -0
  73. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/esp_rainmaker_ctrl.py +0 -0
  74. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/esp_rmaker_prov_local_ctrl_pb2.py +0 -0
  75. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/integration.py +0 -0
  76. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/proto/__init__.py +0 -0
  77. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/proto/proto_lc.py +0 -0
  78. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/prov/__init__.py +0 -0
  79. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/prov/custom_prov.py +0 -0
  80. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/prov/wifi_ctrl.py +0 -0
  81. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/raw_config.py +0 -0
  82. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/raw_params.py +0 -0
  83. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/sec0_pb2.py +0 -0
  84. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/sec1_pb2.py +0 -0
  85. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/sec2_pb2.py +0 -0
  86. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/security/__init__.py +0 -0
  87. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/session_pb2.py +0 -0
  88. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/transport/__init__.py +0 -0
  89. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_local_ctrl/utils/__init__.py +0 -0
  90. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/__init__.py +0 -0
  91. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/config/__init__.py +0 -0
  92. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/config/custom_cloud_config_pb2.py +0 -0
  93. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/config/esp_rmaker_chal_resp_pb2.py +0 -0
  94. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/config/esp_rmaker_claim_pb2.py +0 -0
  95. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/config/esp_rmaker_user_mapping_pb2.py +0 -0
  96. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/proto/__init__.py +0 -0
  97. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/protocomm/__init__.py +0 -0
  98. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/protocomm/python/__init__.py +0 -0
  99. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/protocomm/python/constants_pb2.py +0 -0
  100. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/protocomm/python/sec0_pb2.py +0 -0
  101. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/protocomm/python/sec1_pb2.py +0 -0
  102. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/protocomm/python/sec2_pb2.py +0 -0
  103. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/protocomm/python/session_pb2.py +0 -0
  104. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/prov/__init__.py +0 -0
  105. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/prov/prov_util.py +0 -0
  106. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/prov/user_mapping.py +0 -0
  107. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/security/__init__.py +0 -0
  108. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/transport/__init__.py +0 -0
  109. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/utils/__init__.py +0 -0
  110. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/wifi_provisioning/__init__.py +0 -0
  111. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/wifi_provisioning/python/__init__.py +0 -0
  112. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_config_pb2.py +0 -0
  113. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_constants_pb2.py +0 -0
  114. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_ctrl_pb2.py +0 -0
  115. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_scan_pb2.py +0 -0
  116. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/server_cert/__init__.py +0 -0
  117. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/server_cert/server_cert.pem +0 -0
  118. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/setup.cfg +0 -0
  119. {esp_rainmaker_cli-1.10.0 → esp_rainmaker_cli-1.11.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esp-rainmaker-cli
3
- Version: 1.10.0
3
+ Version: 1.11.1
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
@@ -115,6 +115,13 @@ Changelog
115
115
 
116
116
  All major changes to ESP RainMaker CLI will be documented in this file.
117
117
 
118
+ ## [1.11.1] - 10-Feb-2026
119
+ - Add support for ecdsa in claiming and make it as default, with an option to fall back to the earlier rsa scheme.
120
+
121
+ ## [1.11.0] - 09-Feb-2026
122
+ - Add support for Tags and Metadata during provisioning and also after mapping a user. Check `provision`
123
+ and `node` commands
124
+
118
125
  ## [1.10.0] - 22-Jan-2026
119
126
  ### Added
120
127
  - BLE local control support for `getparams`, `setparams`, and `getnodeconfig` commands during provisioning phase:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esp-rainmaker-cli
3
- Version: 1.10.0
3
+ Version: 1.11.1
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
@@ -115,6 +115,13 @@ Changelog
115
115
 
116
116
  All major changes to ESP RainMaker CLI will be documented in this file.
117
117
 
118
+ ## [1.11.1] - 10-Feb-2026
119
+ - Add support for ecdsa in claiming and make it as default, with an option to fall back to the earlier rsa scheme.
120
+
121
+ ## [1.11.0] - 09-Feb-2026
122
+ - Add support for Tags and Metadata during provisioning and also after mapping a user. Check `provision`
123
+ and `node` commands
124
+
118
125
  ## [1.10.0] - 22-Jan-2026
119
126
  ### Added
120
127
  - BLE local control support for `getparams`, `setparams`, and `getnodeconfig` commands during provisioning phase:
@@ -7,6 +7,7 @@
7
7
  import sys
8
8
  import argparse
9
9
  from rmaker_cmd.node import *
10
+ from rmaker_cmd.node import node_add_tags, node_remove_tags, node_set_metadata, node_delete_metadata
10
11
  from rmaker_cmd.user import signup, login, forgot_password,\
11
12
  get_user_details, logout, set_region_configuration, \
12
13
  profile_list, profile_current, profile_switch, profile_add, profile_remove, delete_user
@@ -483,6 +484,16 @@ def main():
483
484
  help='Do NOT disable challenge-response on device after successful mapping.\n'
484
485
  'Overrides the transport-specific default behavior.')
485
486
 
487
+ provision_parser.add_argument('--tags',
488
+ type=str,
489
+ help='Comma-separated list of tags to attach during node mapping\n'
490
+ '(e.g., "location:pune,name:espressif"). Tags must be in key:value format.')
491
+
492
+ provision_parser.add_argument('--metadata',
493
+ type=str,
494
+ help='Metadata as JSON string to attach during node mapping\n'
495
+ '(e.g., \'{"serial_no": "abc123", "region": "us"}\')')
496
+
486
497
  add_profile_argument(provision_parser)
487
498
  provision_parser.set_defaults(func=provision)
488
499
 
@@ -526,6 +537,14 @@ def main():
526
537
  type=str,
527
538
  help='Special RainMaker Node type (e.g., "camera").')
528
539
 
540
+ claim_parser.add_argument("--key_type", metavar='<key_type>',
541
+ default="ecdsa",
542
+ choices=['rsa', 'ecdsa'],
543
+ help='Cryptographic key type for node certificate (Not applicable for Matter claiming)\n'
544
+ 'ecdsa: ECDSA P-256 (smaller, faster)\n'
545
+ 'rsa: RSA 2048-bit (legacy)\n'
546
+ 'Default: ecdsa')
547
+
529
548
  add_profile_argument(claim_parser)
530
549
  claim_parser.set_defaults(func=claim_node, parser=claim_parser)
531
550
 
@@ -839,6 +858,89 @@ def main():
839
858
  add_profile_argument(group_list_nodes_parser)
840
859
  group_list_nodes_parser.set_defaults(func=group_list_nodes)
841
860
 
861
+ # Node Management (tags, metadata)
862
+ node_parser = subparsers.add_parser('node',
863
+ help='Manage node properties (tags, metadata)')
864
+ node_parser.set_defaults(func=lambda vars=None: node_parser.print_help())
865
+ node_subparsers = node_parser.add_subparsers(dest='node_command', help='Node operations')
866
+
867
+ # node add-tags
868
+ node_add_tags_parser = node_subparsers.add_parser('add-tags',
869
+ help='Add tags to a node',
870
+ description='Add tags to a node. Tags must be in key:value format.')
871
+ node_add_tags_parser.add_argument('nodeid',
872
+ type=str,
873
+ metavar='<nodeid>',
874
+ help='Node ID for the node')
875
+ node_add_tags_parser.add_argument('--tags',
876
+ type=str,
877
+ required=True,
878
+ metavar='<tags>',
879
+ help='Comma-separated list of tags in key:value format\n'
880
+ '(e.g., "location:pune,name:espressif")')
881
+ add_profile_argument(node_add_tags_parser)
882
+ node_add_tags_parser.set_defaults(func=node_add_tags)
883
+
884
+ # node remove-tags
885
+ node_remove_tags_parser = node_subparsers.add_parser('remove-tags',
886
+ help='Remove tags from a node',
887
+ description='Remove specific tags from a node.')
888
+ node_remove_tags_parser.add_argument('nodeid',
889
+ type=str,
890
+ metavar='<nodeid>',
891
+ help='Node ID for the node')
892
+ node_remove_tags_parser.add_argument('--tags',
893
+ type=str,
894
+ required=True,
895
+ metavar='<tags>',
896
+ help='Comma-separated list of tags to remove\n'
897
+ '(e.g., "location:pune,name:espressif")')
898
+ add_profile_argument(node_remove_tags_parser)
899
+ node_remove_tags_parser.set_defaults(func=node_remove_tags)
900
+
901
+ # node set-metadata
902
+ node_set_metadata_parser = node_subparsers.add_parser('set-metadata',
903
+ help='Set or update metadata for a node',
904
+ description='Set or update metadata for a node.\n'
905
+ 'Follows shadow-style merge rules:\n'
906
+ ' - New keys are added, existing keys are updated\n'
907
+ ' - Set a key to null to delete it\n'
908
+ ' - Arrays are overwritten (not merged)')
909
+ node_set_metadata_parser.add_argument('nodeid',
910
+ type=str,
911
+ metavar='<nodeid>',
912
+ help='Node ID for the node')
913
+ node_set_metadata_data_group = node_set_metadata_parser.add_mutually_exclusive_group(required=True)
914
+ node_set_metadata_data_group.add_argument('--data',
915
+ type=str,
916
+ metavar='<json>',
917
+ help='Metadata as JSON string\n'
918
+ '(e.g., \'{"key": "value", "region": "us"}\')')
919
+ node_set_metadata_data_group.add_argument('--filepath',
920
+ type=str,
921
+ metavar='<path>',
922
+ help='Path to JSON file containing metadata')
923
+ add_profile_argument(node_set_metadata_parser)
924
+ node_set_metadata_parser.set_defaults(func=node_set_metadata)
925
+
926
+ # node delete-metadata
927
+ node_delete_metadata_parser = node_subparsers.add_parser('delete-metadata',
928
+ help='Delete metadata from a node',
929
+ description='Delete metadata from a node.\n'
930
+ 'Without --key, deletes all metadata.\n'
931
+ 'With --key, deletes only the specified key(s).')
932
+ node_delete_metadata_parser.add_argument('nodeid',
933
+ type=str,
934
+ metavar='<nodeid>',
935
+ help='Node ID for the node')
936
+ node_delete_metadata_parser.add_argument('--key',
937
+ type=str,
938
+ metavar='<keys>',
939
+ help='Comma-separated list of metadata keys to delete\n'
940
+ '(e.g., "region,name"). If omitted, all metadata is deleted.')
941
+ add_profile_argument(node_delete_metadata_parser)
942
+ node_delete_metadata_parser.set_defaults(func=node_delete_metadata)
943
+
842
944
  # Raw API command
843
945
  raw_api_parser = subparsers.add_parser('raw-api',
844
946
  help='Make raw API calls to RainMaker backend',
@@ -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.10.0"
8
+ VERSION = "1.11.1"
@@ -1664,7 +1664,7 @@ def claim_node(vars=None):
1664
1664
  sys.exit('Invalid MAC address.')
1665
1665
 
1666
1666
  from rmaker_tools.rmaker_claim.claim import claim
1667
- claim(port=vars['port'], node_platform=vars['platform'], mac_addr=vars['mac'], flash_address=vars['addr'], matter=vars['matter'], out_dir=vars['outdir'], node_type=vars['type'])
1667
+ claim(port=vars['port'], node_platform=vars['platform'], mac_addr=vars['mac'], flash_address=vars['addr'], matter=vars['matter'], out_dir=vars['outdir'], node_type=vars['type'], key_type=vars['key_type'])
1668
1668
  except Exception as claim_err:
1669
1669
  log.error(claim_err)
1670
1670
  return
@@ -1878,3 +1878,203 @@ def raw_api_call(vars=None):
1878
1878
  traceback.print_exc()
1879
1879
  return
1880
1880
 
1881
+
1882
+ def node_add_tags(vars=None):
1883
+ """
1884
+ Add tags to a node.
1885
+
1886
+ :param vars: Parameters:
1887
+ `nodeid` as key - Node ID for the node
1888
+ `tags` as key - Comma-separated list of tags (key:value format)
1889
+ `profile` as key - Profile to use for the operation
1890
+ :type vars: dict | None
1891
+
1892
+ :return: None on Success
1893
+ :rtype: None
1894
+ """
1895
+ try:
1896
+ s = get_session_with_profile(vars or {})
1897
+ node_id = vars.get('nodeid')
1898
+ tags_str = vars.get('tags')
1899
+
1900
+ if not node_id:
1901
+ print("Error: Node ID is required.")
1902
+ return
1903
+ if not tags_str:
1904
+ print("Error: Tags are required.")
1905
+ return
1906
+
1907
+ # Parse comma-separated tags
1908
+ tags = [t.strip() for t in tags_str.split(',') if t.strip()]
1909
+
1910
+ # Validate tag format (key:value)
1911
+ for tag in tags:
1912
+ if ':' not in tag:
1913
+ print(f"Error: Invalid tag format '{tag}'. Tags must be in key:value format.")
1914
+ return
1915
+
1916
+ n = node.Node(node_id, s)
1917
+ response = n.update_node_tags(tags)
1918
+
1919
+ if response.get('status') == 'success':
1920
+ print(f"Tags added to node {node_id} successfully.")
1921
+ else:
1922
+ description = response.get('description', 'Unknown error')
1923
+ print(f"Failed to add tags: {description}")
1924
+
1925
+ except Exception as e:
1926
+ log.error(f"Error adding tags: {e}")
1927
+
1928
+
1929
+ def node_remove_tags(vars=None):
1930
+ """
1931
+ Remove tags from a node.
1932
+
1933
+ :param vars: Parameters:
1934
+ `nodeid` as key - Node ID for the node
1935
+ `tags` as key - Comma-separated list of tags to remove (key:value format)
1936
+ `profile` as key - Profile to use for the operation
1937
+ :type vars: dict | None
1938
+
1939
+ :return: None on Success
1940
+ :rtype: None
1941
+ """
1942
+ try:
1943
+ s = get_session_with_profile(vars or {})
1944
+ node_id = vars.get('nodeid')
1945
+ tags_str = vars.get('tags')
1946
+
1947
+ if not node_id:
1948
+ print("Error: Node ID is required.")
1949
+ return
1950
+ if not tags_str:
1951
+ print("Error: Tags are required.")
1952
+ return
1953
+
1954
+ # Parse comma-separated tags
1955
+ tags = [t.strip() for t in tags_str.split(',') if t.strip()]
1956
+
1957
+ n = node.Node(node_id, s)
1958
+ response = n.remove_node_tags(tags)
1959
+
1960
+ if response.get('status') == 'success':
1961
+ print(f"Tags removed from node {node_id} successfully.")
1962
+ else:
1963
+ description = response.get('description', 'Unknown error')
1964
+ print(f"Failed to remove tags: {description}")
1965
+
1966
+ except Exception as e:
1967
+ log.error(f"Error removing tags: {e}")
1968
+
1969
+
1970
+ def node_set_metadata(vars=None):
1971
+ """
1972
+ Set or update metadata for a node. Follows shadow-style merge rules:
1973
+ - New keys are added, existing keys are updated
1974
+ - To delete a specific key, set its value to null in the JSON
1975
+
1976
+ :param vars: Parameters:
1977
+ `nodeid` as key - Node ID for the node
1978
+ `data` as key - JSON string of metadata to set
1979
+ `filepath` as key - Path to JSON file containing metadata
1980
+ `profile` as key - Profile to use for the operation
1981
+ :type vars: dict | None
1982
+
1983
+ :return: None on Success
1984
+ :rtype: None
1985
+ """
1986
+ try:
1987
+ s = get_session_with_profile(vars or {})
1988
+ node_id = vars.get('nodeid')
1989
+
1990
+ if not node_id:
1991
+ print("Error: Node ID is required.")
1992
+ return
1993
+
1994
+ # Parse metadata from --data or --filepath
1995
+ data_str = vars.get('data')
1996
+ filepath = vars.get('filepath')
1997
+
1998
+ if not data_str and not filepath:
1999
+ print("Error: Either --data or --filepath is required.")
2000
+ return
2001
+
2002
+ metadata = None
2003
+ if data_str:
2004
+ try:
2005
+ metadata = json.loads(data_str)
2006
+ except json.JSONDecodeError as e:
2007
+ print(f"Error: Invalid JSON in --data: {e}")
2008
+ return
2009
+ elif filepath:
2010
+ try:
2011
+ with open(filepath, 'r', encoding='utf-8') as f:
2012
+ metadata = json.load(f)
2013
+ except FileNotFoundError:
2014
+ print(f"Error: File not found: {filepath}")
2015
+ return
2016
+ except json.JSONDecodeError as e:
2017
+ print(f"Error: Invalid JSON in file {filepath}: {e}")
2018
+ return
2019
+
2020
+ n = node.Node(node_id, s)
2021
+ response = n.update_node_metadata(metadata)
2022
+
2023
+ if response.get('status') == 'success':
2024
+ print(f"Metadata updated for node {node_id} successfully.")
2025
+ else:
2026
+ description = response.get('description', 'Unknown error')
2027
+ print(f"Failed to update metadata: {description}")
2028
+
2029
+ except Exception as e:
2030
+ log.error(f"Error updating metadata: {e}")
2031
+
2032
+
2033
+ def node_delete_metadata(vars=None):
2034
+ """
2035
+ Delete metadata from a node.
2036
+ Without --key, deletes all metadata.
2037
+ With --key, deletes only the specified key(s).
2038
+
2039
+ :param vars: Parameters:
2040
+ `nodeid` as key - Node ID for the node
2041
+ `key` as key - Comma-separated list of metadata keys to delete (optional)
2042
+ `profile` as key - Profile to use for the operation
2043
+ :type vars: dict | None
2044
+
2045
+ :return: None on Success
2046
+ :rtype: None
2047
+ """
2048
+ try:
2049
+ s = get_session_with_profile(vars or {})
2050
+ node_id = vars.get('nodeid')
2051
+
2052
+ if not node_id:
2053
+ print("Error: Node ID is required.")
2054
+ return
2055
+
2056
+ key_str = vars.get('key')
2057
+
2058
+ n = node.Node(node_id, s)
2059
+
2060
+ if key_str:
2061
+ # Delete specific keys by setting them to None
2062
+ keys = [k.strip() for k in key_str.split(',') if k.strip()]
2063
+ metadata = {k: None for k in keys}
2064
+ response = n.update_node_metadata(metadata)
2065
+ key_list = ', '.join(keys)
2066
+ success_msg = f"Metadata key(s) '{key_list}' deleted from node {node_id} successfully."
2067
+ else:
2068
+ # Delete all metadata by sending null
2069
+ response = n.update_node_metadata(None)
2070
+ success_msg = f"All metadata deleted from node {node_id} successfully."
2071
+
2072
+ if response.get('status') == 'success':
2073
+ print(success_msg)
2074
+ else:
2075
+ description = response.get('description', 'Unknown error')
2076
+ print(f"Failed to delete metadata: {description}")
2077
+
2078
+ except Exception as e:
2079
+ log.error(f"Error deleting metadata: {e}")
2080
+
@@ -123,6 +123,19 @@ def provision(vars=None):
123
123
  passphrase = vars.get('passphrase')
124
124
  no_wifi = vars.get('no_wifi', False)
125
125
 
126
+ # Parse tags and metadata for node mapping
127
+ mapping_tags = None
128
+ mapping_metadata = None
129
+ tags_str = vars.get('tags')
130
+ metadata_str = vars.get('metadata')
131
+ if tags_str:
132
+ mapping_tags = [t.strip() for t in tags_str.split(',') if t.strip()]
133
+ if metadata_str:
134
+ try:
135
+ mapping_metadata = json.loads(metadata_str)
136
+ except json.JSONDecodeError as e:
137
+ raise ValueError(f"Invalid JSON in --metadata: {e}")
138
+
126
139
  # Generate secret key for user-node mapping
127
140
  secret_key = str(uuid.uuid4())
128
141
 
@@ -182,7 +195,9 @@ def provision(vars=None):
182
195
  sec_ver_override=sec_ver,
183
196
  discovery_timeout=discovery_timeout,
184
197
  interactive=True,
185
- disable_on_success=disable_chal_resp
198
+ disable_on_success=disable_chal_resp,
199
+ tags=mapping_tags,
200
+ metadata=mapping_metadata
186
201
  )
187
202
 
188
203
  if success and node_id:
@@ -220,7 +235,9 @@ def provision(vars=None):
220
235
  session=curr_session,
221
236
  no_retry=vars.get('no_retry', False),
222
237
  no_wifi=no_wifi,
223
- disable_chal_resp=disable_chal_resp
238
+ disable_chal_resp=disable_chal_resp,
239
+ tags=mapping_tags,
240
+ metadata=mapping_metadata
224
241
  )
225
242
  except RuntimeError as claim_err:
226
243
  # Handle claim requirement error specifically
@@ -272,7 +289,7 @@ def provision(vars=None):
272
289
  try:
273
290
  log.info('Adding node to user account...')
274
291
  node_obj = node.Node(node_id, curr_session)
275
- request_id = node_obj.add_user_node_mapping(secret_key)
292
+ request_id = node_obj.add_user_node_mapping(secret_key, tags=mapping_tags, metadata=mapping_metadata)
276
293
  if not request_id:
277
294
  print(f'⚠️ Warning: Node provisioned but failed to add to account')
278
295
  log.warning('add_user_node_mapping returned None')
@@ -340,7 +340,7 @@ class Node:
340
340
  log.info(f"Updating parameters for {len(node_params_list)} nodes")
341
341
  return Node._set_params_api(node_params_list, session.config, session.request_header)
342
342
 
343
- def __user_node_mapping(self, secret_key, operation):
343
+ def __user_node_mapping(self, secret_key, operation, tags=None, metadata=None):
344
344
  """
345
345
  Add or remove the user node mapping.
346
346
 
@@ -352,6 +352,12 @@ class Node:
352
352
  'add' or 'remove'
353
353
  :type operation: str
354
354
 
355
+ :param tags: Optional list of tags to attach during mapping (format: ["key:value", ...])
356
+ :type tags: list | None
357
+
358
+ :param metadata: Optional metadata dict to attach during mapping
359
+ :type metadata: dict | None
360
+
355
361
  :raises NetworkError: If there is a network connection issue
356
362
  while adding user node mapping
357
363
  :raises Exception: If there is an HTTP issue or JSON format issue
@@ -371,6 +377,11 @@ class Node:
371
377
  'operation': operation
372
378
  }
373
379
 
380
+ if tags:
381
+ request_payload['tags'] = tags
382
+ if metadata:
383
+ request_payload['metadata'] = metadata
384
+
374
385
  # Get fresh token (will refresh if expired) before making API call
375
386
  # This prevents "Unauthorized" errors if token expired during provisioning
376
387
  try:
@@ -419,7 +430,7 @@ class Node:
419
430
  return response['request_id']
420
431
  return None
421
432
 
422
- def add_user_node_mapping(self, secret_key):
433
+ def add_user_node_mapping(self, secret_key, tags=None, metadata=None):
423
434
  """
424
435
  Add user node mapping.
425
436
 
@@ -427,6 +438,12 @@ class Node:
427
438
  used for User-Node mapping
428
439
  :type secret_key: str
429
440
 
441
+ :param tags: Optional list of tags to attach during mapping (format: ["key:value", ...])
442
+ :type tags: list | None
443
+
444
+ :param metadata: Optional metadata dict to attach during mapping
445
+ :type metadata: dict | None
446
+
430
447
  :raises NetworkError: If there is a network connection issue while
431
448
  adding user node mapping
432
449
  :raises Exception: If there is an HTTP issue while
@@ -438,7 +455,7 @@ class Node:
438
455
  """
439
456
  log.info("Adding user node mapping request with nodeid : " +
440
457
  self.__nodeid)
441
- return self.__user_node_mapping(secret_key, 'add')
458
+ return self.__user_node_mapping(secret_key, 'add', tags=tags, metadata=metadata)
442
459
 
443
460
  def remove_user_node_mapping(self):
444
461
  """
@@ -845,6 +862,163 @@ class Node:
845
862
  raise get_nodes_params_err
846
863
 
847
864
  response = json.loads(response.text)
848
- log.debug("Received shared nodes successfully.")
865
+ log.debug("Received sharing requests successfully.")
866
+
867
+ return response
868
+
869
+ def update_node_tags(self, tags):
870
+ """
871
+ Add tags to a node.
872
+ Uses PUT /v1/user/nodes?node_id=<node_id>
873
+
874
+ :param tags: List of tags to add (format: ["key:value", ...])
875
+ :type tags: list
876
+
877
+ :raises NetworkError: If there is a network connection issue
878
+ :raises Exception: If there is an HTTP issue while updating tags
879
+
880
+ :return: API response on Success
881
+ :rtype: dict
882
+ """
883
+ socket.setdefaulttimeout(10)
884
+ log.info("Adding tags to node : " + self.__nodeid)
885
+ path = 'user/nodes'
886
+ query_parameters = 'node_id=' + self.__nodeid
887
+ url = self.config.get_host() + path + '?' + query_parameters
888
+
889
+ payload = {'tags': tags}
890
+
891
+ try:
892
+ log.debug("Update node tags request url : " + url)
893
+ log.debug("Update node tags request payload : " + json.dumps(payload))
894
+ response = requests.put(url=url,
895
+ headers=self.request_header,
896
+ json=payload,
897
+ verify=configmanager.CERT_FILE,
898
+ timeout=(5.0, 5.0))
899
+ log.debug("Update node tags response : " + response.text)
900
+ response.raise_for_status()
901
+
902
+ except HTTPError as http_err:
903
+ log.debug(http_err)
904
+ raise HttpErrorResponse(response.json())
905
+ except requests.exceptions.SSLError:
906
+ raise SSLError
907
+ except requests.exceptions.ConnectionError:
908
+ raise NetworkError
909
+ except Timeout as time_err:
910
+ log.debug(time_err)
911
+ raise RequestTimeoutError
912
+ except RequestException as req_err:
913
+ log.debug(req_err)
914
+ raise req_err
915
+
916
+ response = json.loads(response.text)
917
+ log.info("Added tags to node successfully.")
918
+ return response
919
+
920
+ def remove_node_tags(self, tags):
921
+ """
922
+ Remove tags from a node.
923
+ Uses DELETE /v1/user/nodes?node_id=<node_id>
924
+
925
+ :param tags: List of tags to remove (format: ["key:value", ...])
926
+ :type tags: list
927
+
928
+ :raises NetworkError: If there is a network connection issue
929
+ :raises Exception: If there is an HTTP issue while removing tags
930
+
931
+ :return: API response on Success
932
+ :rtype: dict
933
+ """
934
+ socket.setdefaulttimeout(10)
935
+ log.info("Removing tags from node : " + self.__nodeid)
936
+ path = 'user/nodes'
937
+ query_parameters = 'node_id=' + self.__nodeid
938
+ url = self.config.get_host() + path + '?' + query_parameters
849
939
 
940
+ payload = {'tags': tags}
941
+
942
+ try:
943
+ log.debug("Remove node tags request url : " + url)
944
+ log.debug("Remove node tags request payload : " + json.dumps(payload))
945
+ response = requests.delete(url=url,
946
+ headers=self.request_header,
947
+ json=payload,
948
+ verify=configmanager.CERT_FILE,
949
+ timeout=(5.0, 5.0))
950
+ log.debug("Remove node tags response : " + response.text)
951
+ response.raise_for_status()
952
+
953
+ except HTTPError as http_err:
954
+ log.debug(http_err)
955
+ raise HttpErrorResponse(response.json())
956
+ except requests.exceptions.SSLError:
957
+ raise SSLError
958
+ except requests.exceptions.ConnectionError:
959
+ raise NetworkError
960
+ except Timeout as time_err:
961
+ log.debug(time_err)
962
+ raise RequestTimeoutError
963
+ except RequestException as req_err:
964
+ log.debug(req_err)
965
+ raise req_err
966
+
967
+ response = json.loads(response.text)
968
+ log.info("Removed tags from node successfully.")
969
+ return response
970
+
971
+ def update_node_metadata(self, metadata):
972
+ """
973
+ Update metadata for a node. Follows shadow-style merge rules:
974
+ - {"key": value} upserts that key
975
+ - {"key": null} deletes that key
976
+ - null (top-level) deletes all metadata
977
+
978
+ Uses PUT /v1/user/nodes?node_id=<node_id>
979
+
980
+ :param metadata: Metadata dict to set/update, or None to delete all
981
+ :type metadata: dict | None
982
+
983
+ :raises NetworkError: If there is a network connection issue
984
+ :raises Exception: If there is an HTTP issue while updating metadata
985
+
986
+ :return: API response on Success
987
+ :rtype: dict
988
+ """
989
+ socket.setdefaulttimeout(10)
990
+ log.info("Updating metadata for node : " + self.__nodeid)
991
+ path = 'user/nodes'
992
+ query_parameters = 'node_id=' + self.__nodeid
993
+ url = self.config.get_host() + path + '?' + query_parameters
994
+
995
+ payload = {'metadata': metadata}
996
+
997
+ try:
998
+ log.debug("Update node metadata request url : " + url)
999
+ log.debug("Update node metadata request payload : " + json.dumps(payload))
1000
+ response = requests.put(url=url,
1001
+ headers=self.request_header,
1002
+ json=payload,
1003
+ verify=configmanager.CERT_FILE,
1004
+ timeout=(5.0, 5.0))
1005
+ log.debug("Update node metadata response : " + response.text)
1006
+ response.raise_for_status()
1007
+
1008
+ except HTTPError as http_err:
1009
+ log.debug(http_err)
1010
+ raise HttpErrorResponse(response.json())
1011
+ except requests.exceptions.SSLError:
1012
+ raise SSLError
1013
+ except requests.exceptions.ConnectionError:
1014
+ raise NetworkError
1015
+ except Timeout as time_err:
1016
+ log.debug(time_err)
1017
+ raise RequestTimeoutError
1018
+ except RequestException as req_err:
1019
+ log.debug(req_err)
1020
+ raise req_err
1021
+
1022
+ response = json.loads(response.text)
1023
+ log.info("Updated node metadata successfully.")
850
1024
  return response