pytbox 0.1.1__tar.gz → 0.1.2__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.

Potentially problematic release.


This version of pytbox might be problematic. Click here for more details.

Files changed (81) hide show
  1. {pytbox-0.1.1/src/pytbox.egg-info → pytbox-0.1.2}/PKG-INFO +1 -1
  2. {pytbox-0.1.1 → pytbox-0.1.2}/pyproject.toml +1 -1
  3. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/base.py +13 -1
  4. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/build_config.py +1 -6
  5. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/instances.toml +3 -0
  6. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.snmp/cisco_interface.toml.j2 +2 -2
  7. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.snmp/cisco_system.toml.j2 +2 -2
  8. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.snmp/h3c_interface.toml.j2 +2 -2
  9. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.snmp/h3c_system.toml.j2 +2 -2
  10. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.snmp/huawei_interface.toml.j2 +2 -2
  11. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.snmp/huawei_system.toml.j2 +2 -2
  12. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.snmp/ruijie_interface.toml.j2 +2 -2
  13. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.snmp/ruijie_system.toml.j2 +2 -2
  14. pytbox-0.1.2/src/pytbox/cli/commands/vm.py +22 -0
  15. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/cli/main.py +2 -0
  16. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/database/victoriametrics.py +36 -10
  17. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/utils/env.py +2 -2
  18. pytbox-0.1.2/src/pytbox/utils/load_config.py +132 -0
  19. pytbox-0.1.2/src/pytbox/utils/load_vm_devfile.py +45 -0
  20. pytbox-0.1.2/src/pytbox/vmware.py +120 -0
  21. {pytbox-0.1.1 → pytbox-0.1.2/src/pytbox.egg-info}/PKG-INFO +1 -1
  22. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox.egg-info/SOURCES.txt +5 -1
  23. pytbox-0.1.2/tests/test_vmware.py +6 -0
  24. pytbox-0.1.1/src/pytbox/utils/load_config.py +0 -86
  25. {pytbox-0.1.1 → pytbox-0.1.2}/MANIFEST.in +0 -0
  26. {pytbox-0.1.1 → pytbox-0.1.2}/README.md +0 -0
  27. {pytbox-0.1.1 → pytbox-0.1.2}/setup.cfg +0 -0
  28. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/alert/alert_handler.py +0 -0
  29. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/alert/ping.py +0 -0
  30. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/alicloud/sls.py +0 -0
  31. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/__init__.py +0 -0
  32. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.cpu/cpu.toml.j2 +0 -0
  33. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.disk/disk.toml.j2 +0 -0
  34. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.diskio/diskio.toml.j2 +0 -0
  35. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.dns_query/dns_query.toml.j2 +0 -0
  36. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.http_response/http_response.toml.j2 +0 -0
  37. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.mem/mem.toml.j2 +0 -0
  38. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.net/net.toml.j2 +0 -0
  39. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.net_response/net_response.toml.j2 +0 -0
  40. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.ping/ping.toml.j2 +0 -0
  41. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.prometheus/prometheus.toml.j2 +0 -0
  42. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/categraf/jinja2/input.vsphere/vsphere.toml.j2 +0 -0
  43. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/cli/__init__.py +0 -0
  44. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/cli/categraf/__init__.py +0 -0
  45. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/cli/categraf/commands.py +0 -0
  46. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/cli/common/__init__.py +0 -0
  47. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/cli/common/options.py +0 -0
  48. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/cli/common/utils.py +0 -0
  49. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/cli/formatters/__init__.py +0 -0
  50. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/cli/formatters/output.py +0 -0
  51. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/cli.py +0 -0
  52. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/common/__init__.py +0 -0
  53. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/database/mongo.py +0 -0
  54. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/dida365.py +0 -0
  55. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/feishu/client.py +0 -0
  56. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/feishu/endpoints.py +0 -0
  57. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/feishu/errors.py +0 -0
  58. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/feishu/helpers.py +0 -0
  59. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/feishu/typing.py +0 -0
  60. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/log/logger.py +0 -0
  61. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/log/victorialog.py +0 -0
  62. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/mail/alimail.py +0 -0
  63. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/mail/client.py +0 -0
  64. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/network/meraki.py +0 -0
  65. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/onepassword_connect.py +0 -0
  66. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/onepassword_sa.py +0 -0
  67. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/utils/ping_checker.py +0 -0
  68. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/utils/response.py +0 -0
  69. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/utils/richutils.py +0 -0
  70. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/utils/timeutils.py +0 -0
  71. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox/win/ad.py +0 -0
  72. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox.egg-info/dependency_links.txt +0 -0
  73. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox.egg-info/entry_points.txt +0 -0
  74. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox.egg-info/requires.txt +0 -0
  75. {pytbox-0.1.1 → pytbox-0.1.2}/src/pytbox.egg-info/top_level.txt +0 -0
  76. {pytbox-0.1.1 → pytbox-0.1.2}/tests/test_base.py +0 -0
  77. {pytbox-0.1.1 → pytbox-0.1.2}/tests/test_feishu.py +0 -0
  78. {pytbox-0.1.1 → pytbox-0.1.2}/tests/test_logger.py +0 -0
  79. {pytbox-0.1.1 → pytbox-0.1.2}/tests/test_onepassword_connect.py +0 -0
  80. {pytbox-0.1.1 → pytbox-0.1.2}/tests/test_onepassword_sa.py +0 -0
  81. {pytbox-0.1.1 → pytbox-0.1.2}/tests/test_victoriametrics.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytbox
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: A collection of Python integrations and utilities (Feishu, Dida365, VictoriaMetrics, ...)
5
5
  Author-email: mingming hou <houm01@foxmail.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pytbox"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  description = "A collection of Python integrations and utilities (Feishu, Dida365, VictoriaMetrics, ...)"
9
9
  authors = [{ name = "mingming hou", email = "houm01@foxmail.com" }]
10
10
  license = "MIT"
@@ -10,9 +10,12 @@ from pytbox.alert.alert_handler import AlertHandler
10
10
  from pytbox.log.logger import AppLogger
11
11
  from pytbox.win.ad import ADClient
12
12
  from pytbox.network.meraki import Meraki
13
+ from pytbox.utils.env import get_env_by_os_environment
14
+ from pytbox.vmware import VMwareClient
13
15
 
14
16
  config = load_config_by_file(path='/workspaces/pytbox/tests/alert/config_dev.toml', oc_vault_id=os.environ.get('oc_vault_id'))
15
17
 
18
+
16
19
  def get_mongo(collection):
17
20
  return Mongo(
18
21
  host=config['mongo']['host'],
@@ -61,4 +64,13 @@ def get_logger(app):
61
64
  # password=config['ad']['prod']['AD_PASSWORD']
62
65
  # )
63
66
 
64
- meraki = Meraki(api_key=config['meraki']['api_key'], organization_id=config['meraki']['organization_id'])
67
+ env = get_env_by_os_environment(check_key='ENV')
68
+ meraki = Meraki(api_key=config['meraki']['api_key'], organization_id=config['meraki']['organization_id'])
69
+
70
+ vmware_test = VMwareClient(
71
+ host=config['vmware']['test']['host'],
72
+ username=config['vmware']['test']['username'],
73
+ password=config['vmware']['test']['password'],
74
+ version=config['vmware']['test']['version'],
75
+ proxies=config['vmware']['test']['proxies']
76
+ )
@@ -114,16 +114,11 @@ class BuildConfig:
114
114
  device_templates = glob(str(jinja2_dir / f'{device_type}_*.toml.j2'))
115
115
  if not device_templates:
116
116
  continue
117
-
118
117
  for tmpl_path in device_templates:
119
118
  tmpl_name = os.path.basename(tmpl_path)
120
- # 例如 h3c_system.toml.j2 -> h3c_system
121
119
  base_name = tmpl_name.replace('.toml.j2', '')
122
-
123
120
  template = self._get_template(f'input.snmp/{tmpl_name}')
124
- # 修复数据结构:模板期望的是数组,每个元素是字典
125
- # instances 是 [{"udp://10.1.1.1:161": {...}, "udp://10.1.1.2:161": {...}}, ...]
126
- render_data = template.render(instances=instances)
121
+ render_data = template.render(instances=instances, config=self.instances['snmp']['config'])
127
122
 
128
123
  target_dir = Path(self.output_dir) / 'input.snmp'
129
124
  if not target_dir.exists():
@@ -25,6 +25,9 @@
25
25
  "119.29.29.29_baidu.com" = { dns_server = "119.29.29.29", domains = "www.baidu.com", labels = { name = "x", env = "prod" } }
26
26
 
27
27
  [snmp]
28
+ [snmp.config]
29
+ timeout = '120s'
30
+ path = "/usr/share/snmp/mibs"
28
31
  # 支持 h3c,huawei,cisco,ruijie
29
32
  [[snmp.instances.h3c]]
30
33
  "10.1.1.1:161" = { version = 2, community = "public" }
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import click
4
+ from pytbox.database.victoriametrics import VictoriaMetrics
5
+ from pytbox.utils.richutils import RichUtils
6
+
7
+ rich_utils = RichUtils()
8
+
9
+ @click.group()
10
+ def vm_group():
11
+ """VictoriaMetrics 查询工具"""
12
+ pass
13
+
14
+
15
+ @vm_group.command('query')
16
+ @click.option('--url', '-u', type=str, required=True)
17
+ @click.option('--query', '-q', type=str, required=True)
18
+ def query(url, query):
19
+ """查询 VM 数据"""
20
+ vm_client = VictoriaMetrics(url=url)
21
+ r = vm_client.query(query, output_format='json')
22
+ rich_utils.print(msg=r)
@@ -5,6 +5,7 @@ Pytbox 主命令行入口
5
5
 
6
6
  import click
7
7
  from .categraf import categraf_group
8
+ from .commands.vm import vm_group
8
9
 
9
10
 
10
11
  @click.group()
@@ -16,6 +17,7 @@ def main():
16
17
 
17
18
  # 注册子命令组
18
19
  main.add_command(categraf_group, name='categraf')
20
+ main.add_command(vm_group, name='vm')
19
21
 
20
22
 
21
23
  if __name__ == "__main__":
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
+ import json
3
4
  from typing import Literal, Optional
4
5
  import requests
5
6
  from ..utils.response import ReturnResponse
7
+ from ..utils.load_vm_devfile import load_dev_file
6
8
 
7
9
 
8
10
  class VictoriaMetrics:
@@ -16,13 +18,13 @@ class VictoriaMetrics:
16
18
  'Accept': 'application/json'
17
19
  })
18
20
 
19
- def query(self, query: str) -> ReturnResponse:
21
+ def query(self, query: str, output_format: Literal['json']=None) -> ReturnResponse:
20
22
  '''
21
23
  查询指标数据
22
-
24
+
23
25
  Args:
24
26
  query (str): 查询语句
25
-
27
+
26
28
  Returns:
27
29
  dict: 查询结果
28
30
  '''
@@ -32,13 +34,32 @@ class VictoriaMetrics:
32
34
  timeout=self.timeout,
33
35
  params={"query": query}
34
36
  )
35
- if r.json().get("status") == "success":
36
- if r.json()['data']['result']:
37
- return ReturnResponse(code=0, msg=f"[{query}] 查询成功!", data=r.json()['data']['result'])
37
+ res_json = r.json()
38
+ status = res_json.get("status")
39
+ result = res_json.get("data", {}).get("result", [])
40
+ is_json = output_format == 'json'
41
+
42
+ if status == "success":
43
+ if result:
44
+ code = 0
45
+ msg = f"[{query}] 查询成功!"
46
+ data = result
38
47
  else:
39
- return ReturnResponse(code=2, msg=f"[{query}] 没有查询到结果", data=r.json())
48
+ code = 2
49
+ msg = f"[{query}] 没有查询到结果"
50
+ data = res_json
40
51
  else:
41
- return ReturnResponse(code=1, msg=f"[{query}] 查询失败: {r.json().get('error')}", data=r.json())
52
+ code = 1
53
+ msg = f"[{query}] 查询失败: {res_json.get('error')}"
54
+ data = res_json
55
+
56
+ resp = ReturnResponse(code=code, msg=msg, data=data)
57
+
58
+ if is_json:
59
+ json_result = json.dumps(resp.__dict__, ensure_ascii=False)
60
+ return json_result
61
+ else:
62
+ return resp
42
63
 
43
64
  def get_labels(self, metric_name: str) -> ReturnResponse:
44
65
  url = f"{self.url}/api/v1/series?match[]={metric_name}"
@@ -49,7 +70,7 @@ class VictoriaMetrics:
49
70
  else:
50
71
  return ReturnResponse(code=1, msg=f"metric name: {metric_name} 查询失败")
51
72
 
52
- def check_ping_result(self, target: str, last_minute: int=10) -> ReturnResponse:
73
+ def check_ping_result(self, target: str, last_minute: int=10, env: str='prod', dev_file: str='') -> ReturnResponse:
53
74
  '''
54
75
  检查ping结果
55
76
  '''
@@ -62,13 +83,18 @@ class VictoriaMetrics:
62
83
  if last_minute:
63
84
  query = query + f"[{last_minute}m]"
64
85
 
65
- r = self.query(query=query)
86
+ if env == 'dev':
87
+ r = load_dev_file(dev_file)
88
+ else:
89
+ r = self.query(query=query)
90
+
66
91
  if r.code == 0:
67
92
  values = r.data[0]['values']
68
93
  if len(values) == 2 and values[1] == "0":
69
94
  code = 0
70
95
  msg = f"已检查 {target} 最近 {last_minute} 分钟是正常的!"
71
96
  else:
97
+
72
98
  if all(str(item[1]) == "1" for item in values):
73
99
  code = 1
74
100
  msg = f"已检查 {target} 最近 {last_minute} 分钟是异常的!"
@@ -17,14 +17,14 @@ def get_env_by_file_exist(file_path: str) -> Literal['prod', 'dev']:
17
17
  return 'dev'
18
18
 
19
19
 
20
- def get_env_by_os_environment() -> Literal['dev', 'prod']:
20
+ def get_env_by_os_environment(check_key: str='ENV') -> Literal['dev', 'prod']:
21
21
  '''
22
22
  根据环境变量获取环境
23
23
 
24
24
  Returns:
25
25
  Literal['dev', 'prod']: 环境,dev 表示开发环境,prod 表示生产环境
26
26
  '''
27
- if os.getenv('ENV') == 'dev':
27
+ if os.getenv(check_key) == 'dev':
28
28
  return 'dev'
29
29
  else:
30
30
  return 'prod'
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import json
5
+
6
+ try:
7
+ import toml
8
+ except ImportError:
9
+ import tomllib as toml # Python 3.11+ fallback
10
+ from pytbox.onepassword_connect import OnePasswordConnect
11
+
12
+
13
+ def _replace_values(data, oc=None, jsonfile_path=None):
14
+ """
15
+ 递归处理配置数据,替换 1password、password 和 jsonfile 开头的值
16
+
17
+ Args:
18
+ data: 配置数据(dict, list, str 等)
19
+ oc: OnePasswordConnect 实例
20
+ jsonfile_path: JSON 文件路径
21
+
22
+ Returns:
23
+ 处理后的数据
24
+ """
25
+ if isinstance(data, dict):
26
+ result = {}
27
+ for k, v in data.items():
28
+ result[k] = _replace_values(v, oc, jsonfile_path)
29
+ return result
30
+ elif isinstance(data, list):
31
+ return [_replace_values(item, oc, jsonfile_path) for item in data]
32
+ elif isinstance(data, str):
33
+ # 处理 1password,item_id,field_name 格式
34
+ if data.startswith("1password,") and oc:
35
+ parts = data.split(",")
36
+ if len(parts) >= 3:
37
+ item_id = parts[1]
38
+ field_name = parts[2]
39
+ try:
40
+ # 通过 item_id 获取项目,然后从字段中提取对应值
41
+ item = oc.get_item(item_id)
42
+ for field in item.fields:
43
+ if field.label == field_name:
44
+ return field.value
45
+ except (AttributeError, KeyError, ValueError):
46
+ pass
47
+ return data # 如果找不到字段,返回原始值
48
+ # 处理 password,item_id,field_name 格式
49
+ elif data.startswith("password,") and oc:
50
+ parts = data.split(",")
51
+ if len(parts) >= 3:
52
+ item_id = parts[1]
53
+ field_name = parts[2]
54
+ try:
55
+ # 通过 item_id 获取项目,然后从字段中提取对应值
56
+ item = oc.get_item(item_id)
57
+ for field in item.fields:
58
+ if field.label == field_name:
59
+ return field.value
60
+ except (AttributeError, KeyError, ValueError):
61
+ pass
62
+ return data # 如果找不到字段,返回原始值
63
+ # 处理 jsonfile,key 格式
64
+ elif data.startswith("jsonfile,"):
65
+ parts = data.split(",", 1) # 只分割一次,防止 key 中包含逗号
66
+ if len(parts) >= 2:
67
+ key = parts[1]
68
+
69
+ # 尝试从 JSON 文件获取值
70
+ if jsonfile_path and os.path.exists(jsonfile_path):
71
+ try:
72
+ with open(jsonfile_path, 'r', encoding='utf-8') as f:
73
+ json_data = json.load(f)
74
+ # 支持嵌套键,如 "db.password"
75
+ value = json_data
76
+ for k in key.split('.'):
77
+ if isinstance(value, dict) and k in value:
78
+ value = value[k]
79
+ else:
80
+ value = None
81
+ break
82
+ if value is not None:
83
+ return value
84
+ except (json.JSONDecodeError, FileNotFoundError, KeyError):
85
+ pass
86
+
87
+ # 如果从 JSON 文件获取失败,尝试从环境变量获取
88
+ env_value = os.getenv(key)
89
+ if env_value is not None:
90
+ return env_value
91
+
92
+ return data # 如果都获取不到,返回原始值
93
+ return data
94
+ else:
95
+ return data
96
+
97
+
98
+ def load_config_by_file(
99
+ path: str='/workspaces/pytbox/src/pytbox/alert/config/config.toml',
100
+ oc_vault_id: str=None,
101
+ jsonfile: str="/data/jsonfile.json",
102
+ ) -> dict:
103
+ '''
104
+ 从文件加载配置,支持 1password 集成
105
+
106
+ Args:
107
+ path (str, optional): 配置文件路径. Defaults to '/workspaces/pytbox/src/pytbox/alert/config/config.toml'.
108
+ oc_vault_id (str, optional): OnePassword vault ID,如果提供则启用 1password 集成
109
+
110
+ Returns:
111
+ dict: 配置字典
112
+ '''
113
+ with open(path, 'r', encoding='utf-8') as f:
114
+ if path.endswith('.toml'):
115
+ config = toml.load(f)
116
+ else:
117
+ # 如果不是 toml 文件,假设是其他格式,这里可以扩展
118
+ config = json.load(f)
119
+
120
+ # 处理配置值替换
121
+ oc = None
122
+ if oc_vault_id:
123
+ oc = OnePasswordConnect(vault_id=oc_vault_id)
124
+
125
+ # 替换配置中的特殊值(1password, password, jsonfile)
126
+ config = _replace_values(config, oc, jsonfile)
127
+
128
+ return config
129
+
130
+
131
+ if __name__ == "__main__":
132
+ print(load_config_by_file(path='/workspaces/pytbox/tests/alert/config_dev.toml', oc_vault_id="hcls5uxuq5dmxorw6rfewefdsa"))
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import json
4
+ from pytbox.utils.response import ReturnResponse
5
+
6
+
7
+ def load_dev_file(file_path: str) -> ReturnResponse:
8
+ """从开发环境文件加载数据并返回 ReturnResponse 对象
9
+
10
+ Args:
11
+ file_path: JSON 文件路径
12
+
13
+ Returns:
14
+ ReturnResponse: 包装后的响应对象
15
+ """
16
+ try:
17
+ with open(file_path, 'r', encoding='utf-8') as f:
18
+ data = json.load(f)
19
+
20
+ # 如果已经是 ReturnResponse 格式的字典,直接转换
21
+ if isinstance(data, dict) and 'code' in data and 'msg' in data:
22
+ return ReturnResponse(
23
+ code=data.get('code', 0),
24
+ msg=data.get('msg', ''),
25
+ data=data.get('data', None)
26
+ )
27
+ else:
28
+ # 如果是其他格式,包装成成功响应
29
+ return ReturnResponse(
30
+ code=0,
31
+ msg='开发文件加载成功',
32
+ data=data
33
+ )
34
+ except FileNotFoundError:
35
+ return ReturnResponse(
36
+ code=4,
37
+ msg=f'开发文件未找到: {file_path}',
38
+ data=None
39
+ )
40
+ except json.JSONDecodeError as e:
41
+ return ReturnResponse(
42
+ code=1,
43
+ msg=f'JSON 解析错误: {str(e)}',
44
+ data=None
45
+ )
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from typing import Any, Dict, Optional, Union, Literal
5
+ import urllib3
6
+ urllib3.disable_warnings()
7
+ import requests
8
+ from requests.auth import HTTPBasicAuth
9
+
10
+ from .utils.response import ReturnResponse
11
+
12
+
13
+ class VMwareClient:
14
+ """VMware vSphere Automation API 客户端。
15
+
16
+ 支持多种认证方式:
17
+ 1. Basic Auth - HTTP 基础认证
18
+ 2. API Key Auth - 使用会话 ID 认证
19
+ 3. Federated Identity Auth - 联合身份认证(Bearer Token)
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ host: str=None,
25
+ username: str=None,
26
+ password: str=None,
27
+ version: Literal['6.7', '7.0']='6.7',
28
+ proxies: dict=None,
29
+ verify_ssl: bool = False,
30
+ timeout: int = 30,
31
+ ) -> None:
32
+ """初始化 VMware 客户端。
33
+
34
+ Args:
35
+ host: vCenter Server 主机地址(例如:https://vcenter.example.com)
36
+ verify_ssl: 是否验证 SSL 证书
37
+ timeout: 请求超时时间(秒)
38
+ proxy_host: 代理服务器主机地址
39
+ proxy_port: 代理服务器端口
40
+ proxy_username: 代理认证用户名(可选)
41
+ proxy_password: 代理认证密码(可选)
42
+ """
43
+ self.host = host
44
+ self.username = username
45
+ self.password = password
46
+ self.version = version
47
+ self.proxies = proxies
48
+ self.verify_ssl = verify_ssl
49
+ self.timeout = timeout
50
+ self.session_id: Optional[str] = None
51
+
52
+ self.headers = {
53
+ "vmware-api-session-id": self.get_session()
54
+ }
55
+
56
+ def get_session(self) -> str:
57
+ """获取 VMware vSphere API 会话 ID。
58
+
59
+ Returns:
60
+ 会话 ID 字符串
61
+ """
62
+ if self.version == '6.7':
63
+ url = f"{self.host}/rest/com/vmware/cis/session"
64
+ else:
65
+ url = f"{self.host}/api/session"
66
+
67
+ response = requests.post(
68
+ url,
69
+ auth=HTTPBasicAuth(self.username, self.password),
70
+ timeout=self.timeout,
71
+ verify=self.verify_ssl,
72
+ proxies=self.proxies
73
+ )
74
+
75
+ if response.status_code == 200 or response.status_code == 201:
76
+ # vSphere API 通常直接返回 session ID 字符串
77
+ session_id = response.json()
78
+ try:
79
+ return session_id['value']
80
+ except Exception:
81
+ return session_id
82
+ else:
83
+ return f"认证失败: {response.status_code} - {response.text}"
84
+
85
+ def get_vm_list(self) -> ReturnResponse:
86
+ '''
87
+ _summary_
88
+
89
+ Returns:
90
+ ReturnResponse: _description_
91
+ '''
92
+ if self.version == '6.7':
93
+ url = f"{self.host}/rest/vcenter/vm"
94
+ else:
95
+ url = f"{self.host}/api/vcenter/vm"
96
+ response = requests.get(url, headers=self.headers, timeout=self.timeout, verify=False, proxies=self.proxies)
97
+ if response.status_code == 200:
98
+ if self.version == '6.7':
99
+ data = response.json().get('value')
100
+ else:
101
+ data = response.json()
102
+ return ReturnResponse(code=0, msg=f'成功获取到 {len(data)} 台虚拟机', data=data)
103
+ else:
104
+ return ReturnResponse(code=1, msg='error', data=response.json())
105
+
106
+ def get_vm(self, vm_id):
107
+ if self.version == '6.7':
108
+ url = f"{self.host}/rest/vcenter/vm/{vm_id}"
109
+ else:
110
+ url = f"{self.host}/api/vcenter/vm/{vm_id}"
111
+ response = requests.get(url, headers=self.headers, timeout=self.timeout, verify=False, proxies=self.proxies)
112
+ if response.status_code == 200:
113
+ return ReturnResponse(code=0, msg='成功获取到虚拟机', data=response.json())
114
+ else:
115
+ return ReturnResponse(code=1, msg=f"{response.status_code}, {response.text}", data=response.json())
116
+
117
+ # 使用示例
118
+ if __name__ == "__main__":
119
+ pass
120
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytbox
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: A collection of Python integrations and utilities (Feishu, Dida365, VictoriaMetrics, ...)
5
5
  Author-email: mingming hou <houm01@foxmail.com>
6
6
  License-Expression: MIT
@@ -6,6 +6,7 @@ src/pytbox/cli.py
6
6
  src/pytbox/dida365.py
7
7
  src/pytbox/onepassword_connect.py
8
8
  src/pytbox/onepassword_sa.py
9
+ src/pytbox/vmware.py
9
10
  src/pytbox.egg-info/PKG-INFO
10
11
  src/pytbox.egg-info/SOURCES.txt
11
12
  src/pytbox.egg-info/dependency_links.txt
@@ -41,6 +42,7 @@ src/pytbox/cli/__init__.py
41
42
  src/pytbox/cli/main.py
42
43
  src/pytbox/cli/categraf/__init__.py
43
44
  src/pytbox/cli/categraf/commands.py
45
+ src/pytbox/cli/commands/vm.py
44
46
  src/pytbox/cli/common/__init__.py
45
47
  src/pytbox/cli/common/options.py
46
48
  src/pytbox/cli/common/utils.py
@@ -61,6 +63,7 @@ src/pytbox/mail/client.py
61
63
  src/pytbox/network/meraki.py
62
64
  src/pytbox/utils/env.py
63
65
  src/pytbox/utils/load_config.py
66
+ src/pytbox/utils/load_vm_devfile.py
64
67
  src/pytbox/utils/ping_checker.py
65
68
  src/pytbox/utils/response.py
66
69
  src/pytbox/utils/richutils.py
@@ -71,4 +74,5 @@ tests/test_feishu.py
71
74
  tests/test_logger.py
72
75
  tests/test_onepassword_connect.py
73
76
  tests/test_onepassword_sa.py
74
- tests/test_victoriametrics.py
77
+ tests/test_victoriametrics.py
78
+ tests/test_vmware.py
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from pytbox.base import vmware_test
4
+
5
+ r = vmware_test.get_vm_list()
6
+ print(r)
@@ -1,86 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
-
4
- try:
5
- import toml
6
- except ImportError:
7
- import tomllib as toml # Python 3.11+ fallback
8
- from pytbox.onepassword_connect import OnePasswordConnect
9
-
10
-
11
- def _replace_1password_values(data, oc):
12
- """
13
- 递归处理配置数据,替换 1password 和 password 开头的值
14
-
15
- Args:
16
- data: 配置数据(dict, list, str 等)
17
- oc: OnePasswordConnect 实例
18
-
19
- Returns:
20
- 处理后的数据
21
- """
22
- if isinstance(data, dict):
23
- result = {}
24
- for k, v in data.items():
25
- result[k] = _replace_1password_values(v, oc)
26
- return result
27
- elif isinstance(data, list):
28
- return [_replace_1password_values(item, oc) for item in data]
29
- elif isinstance(data, str):
30
- # 处理 1password,item_id,field_name 格式
31
- if data.startswith("1password,"):
32
- parts = data.split(",")
33
- if len(parts) >= 3:
34
- item_id = parts[1]
35
- field_name = parts[2]
36
- # 通过 item_id 获取项目,然后从字段中提取对应值
37
- item = oc.get_item(item_id)
38
- for field in item.fields:
39
- if field.label == field_name:
40
- return field.value
41
- return data # 如果找不到字段,返回原始值
42
- # 处理 password,item_id,field_name 格式
43
- elif data.startswith("password,"):
44
- parts = data.split(",")
45
- if len(parts) >= 3:
46
- item_id = parts[1]
47
- field_name = parts[2]
48
- # 通过 item_id 获取项目,然后从字段中提取对应值
49
- item = oc.get_item(item_id)
50
- for field in item.fields:
51
- if field.label == field_name:
52
- return field.value
53
- return data # 如果找不到字段,返回原始值
54
- return data
55
- else:
56
- return data
57
-
58
-
59
- def load_config_by_file(path: str='/workspaces/pytbox/src/pytbox/alert/config/config.toml', oc_vault_id: str=None) -> dict:
60
- '''
61
- 从文件加载配置,支持 1password 集成
62
-
63
- Args:
64
- path (str, optional): 配置文件路径. Defaults to '/workspaces/pytbox/src/pytbox/alert/config/config.toml'.
65
- oc_vault_id (str, optional): OnePassword vault ID,如果提供则启用 1password 集成
66
-
67
- Returns:
68
- dict: 配置字典
69
- '''
70
- with open(path, 'r', encoding='utf-8') as f:
71
- if path.endswith('.toml'):
72
- config = toml.load(f)
73
- else:
74
- # 如果不是 toml 文件,假设是其他格式,这里可以扩展
75
- import json
76
- config = json.load(f)
77
-
78
- if oc_vault_id:
79
- oc = OnePasswordConnect(vault_id=oc_vault_id)
80
- config = _replace_1password_values(config, oc)
81
-
82
- return config
83
-
84
-
85
- if __name__ == "__main__":
86
- print(load_config_by_file(path='/workspaces/pytbox/tests/alert/config_dev.toml', oc_vault_id="hcls5uxuq5dmxorw6rfewefdsa"))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes