skyplatform-iam 1.0.3__py3-none-any.whl → 1.0.5__py3-none-any.whl

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.
skyplatform_iam/config.py CHANGED
@@ -3,18 +3,21 @@ SkyPlatform IAM SDK 配置模块
3
3
  """
4
4
  import os
5
5
  import fnmatch
6
- from typing import Optional, List
7
- from pydantic import BaseModel, Field
6
+ import logging
7
+ from typing import Optional, List, Dict, Any
8
+ from pydantic import BaseModel, Field, validator
8
9
  from dotenv import load_dotenv
9
10
 
10
11
  # 加载环境变量
11
12
  load_dotenv()
12
13
 
14
+ logger = logging.getLogger(__name__)
15
+
13
16
 
14
17
  class AuthConfig(BaseModel):
15
18
  """
16
19
  认证配置类
17
- 支持环境变量和代码配置
20
+ 支持环境变量和代码配置,增强配置验证和管理功能
18
21
  """
19
22
  # IAM服务配置
20
23
  agenterra_iam_host: str
@@ -30,32 +33,184 @@ class AuthConfig(BaseModel):
30
33
 
31
34
  # 白名单路径配置(实例变量)
32
35
  whitelist_paths: List[str] = Field(default_factory=list)
36
+
37
+ # 连接配置
38
+ timeout: int = 30
39
+ max_retries: int = 3
40
+
41
+ # 缓存配置
42
+ enable_cache: bool = True
43
+ cache_ttl: int = 300 # 5分钟
33
44
 
34
45
  class Config:
35
- env_prefix = "AGENTERRA_"
46
+ env_prefix = "SKYPLATFORM_"
47
+ validate_assignment = True
48
+
49
+ @validator('agenterra_iam_host')
50
+ def validate_iam_host(cls, v):
51
+ """验证IAM主机地址"""
52
+ if not v:
53
+ raise ValueError("agenterra_iam_host不能为空")
54
+ if not (v.startswith('http://') or v.startswith('https://')):
55
+ raise ValueError("agenterra_iam_host必须以http://或https://开头")
56
+ return v.rstrip('/') # 移除末尾的斜杠
57
+
58
+ @validator('server_name')
59
+ def validate_server_name(cls, v):
60
+ """验证服务名称"""
61
+ if not v or not v.strip():
62
+ raise ValueError("server_name不能为空")
63
+ return v.strip()
64
+
65
+ @validator('access_key')
66
+ def validate_access_key(cls, v):
67
+ """验证访问密钥"""
68
+ if not v or not v.strip():
69
+ raise ValueError("access_key不能为空")
70
+ if len(v.strip()) < 8:
71
+ raise ValueError("access_key长度不能少于8个字符")
72
+ return v.strip()
73
+
74
+ @validator('timeout')
75
+ def validate_timeout(cls, v):
76
+ """验证超时时间"""
77
+ if v <= 0:
78
+ raise ValueError("timeout必须大于0")
79
+ return v
80
+
81
+ @validator('max_retries')
82
+ def validate_max_retries(cls, v):
83
+ """验证最大重试次数"""
84
+ if v < 0:
85
+ raise ValueError("max_retries不能小于0")
86
+ return v
87
+
88
+ @validator('cache_ttl')
89
+ def validate_cache_ttl(cls, v):
90
+ """验证缓存TTL"""
91
+ if v <= 0:
92
+ raise ValueError("cache_ttl必须大于0")
93
+ return v
36
94
 
37
95
  @classmethod
38
- def from_env(cls) -> "AuthConfig":
96
+ def from_env(cls, prefix: str = "SKYPLATFORM_") -> "AuthConfig":
39
97
  """
40
98
  从环境变量创建配置
99
+
100
+ Args:
101
+ prefix: 环境变量前缀,默认为SKYPLATFORM_
102
+
103
+ Returns:
104
+ AuthConfig: 配置实例
105
+
106
+ Raises:
107
+ ValueError: 配置验证失败
41
108
  """
42
- return cls(
43
- agenterra_iam_host=os.environ.get('AGENTERRA_IAM_HOST', ''),
44
- server_name=os.environ.get('AGENTERRA_SERVER_NAME', ''),
45
- access_key=os.environ.get('AGENTERRA_ACCESS_KEY', ''),
46
- enable_debug=os.environ.get('AGENTERRA_ENABLE_DEBUG', 'false').lower() == 'true',
47
- whitelist_paths=[] # 初始化空的白名单路径列表
109
+ logger.info(f"从环境变量加载配置,前缀: {prefix}")
110
+
111
+ # 支持多种环境变量前缀(向后兼容)
112
+ def get_env_value(key: str, default: str = '') -> str:
113
+ # 优先使用新前缀
114
+ value = os.environ.get(f"{prefix}{key}", '')
115
+ if not value:
116
+ # 回退到旧前缀
117
+ value = os.environ.get(f"AGENTERRA_{key}", default)
118
+ return value
119
+
120
+ # 解析白名单路径
121
+ whitelist_paths_str = get_env_value('WHITELIST_PATHS', '')
122
+ whitelist_paths = []
123
+ if whitelist_paths_str:
124
+ whitelist_paths = [path.strip() for path in whitelist_paths_str.split(',') if path.strip()]
125
+
126
+ config = cls(
127
+ agenterra_iam_host=get_env_value('IAM_HOST'),
128
+ server_name=get_env_value('SERVER_NAME'),
129
+ access_key=get_env_value('ACCESS_KEY'),
130
+ enable_debug=get_env_value('ENABLE_DEBUG', 'false').lower() == 'true',
131
+ whitelist_paths=whitelist_paths,
132
+ timeout=int(get_env_value('TIMEOUT', '30')),
133
+ max_retries=int(get_env_value('MAX_RETRIES', '3')),
134
+ enable_cache=get_env_value('ENABLE_CACHE', 'true').lower() == 'true',
135
+ cache_ttl=int(get_env_value('CACHE_TTL', '300'))
48
136
  )
137
+
138
+ logger.info(f"配置加载完成: server_name={config.server_name}, "
139
+ f"iam_host={config.agenterra_iam_host}, "
140
+ f"whitelist_paths_count={len(config.whitelist_paths)}")
141
+
142
+ return config
143
+
144
+ def validate_config(self) -> None:
145
+ """
146
+ 验证配置完整性
147
+
148
+ Raises:
149
+ ValueError: 配置验证失败
150
+ """
151
+ logger.debug("开始验证配置完整性")
152
+
153
+ # Pydantic会自动调用validator,这里只需要检查业务逻辑
154
+ if not self.agenterra_iam_host:
155
+ raise ValueError("agenterra_iam_host不能为空")
156
+ if not self.server_name:
157
+ raise ValueError("server_name不能为空")
158
+ if not self.access_key:
159
+ raise ValueError("access_key不能为空")
160
+
161
+ logger.info("配置验证通过")
49
162
 
50
- def validate_config(self) -> bool:
163
+ def merge_config(self, other: "AuthConfig") -> "AuthConfig":
164
+ """
165
+ 合并配置,other的非空值会覆盖当前配置
166
+
167
+ Args:
168
+ other: 要合并的配置
169
+
170
+ Returns:
171
+ AuthConfig: 合并后的新配置实例
51
172
  """
52
- 验证配置是否完整
173
+ logger.debug("开始合并配置")
174
+
175
+ # 获取当前配置的字典表示
176
+ current_dict = self.dict()
177
+ other_dict = other.dict()
178
+
179
+ # 合并配置
180
+ merged_dict = current_dict.copy()
181
+ for key, value in other_dict.items():
182
+ if key == 'whitelist_paths':
183
+ # 白名单路径需要合并而不是覆盖
184
+ merged_paths = list(set(current_dict[key] + value))
185
+ merged_dict[key] = merged_paths
186
+ elif value: # 只有非空值才覆盖
187
+ merged_dict[key] = value
188
+
189
+ logger.debug(f"配置合并完成,合并后的配置: {merged_dict}")
190
+ return AuthConfig(**merged_dict)
191
+
192
+ def to_dict(self) -> Dict[str, Any]:
193
+ """
194
+ 转换为字典格式
195
+
196
+ Returns:
197
+ Dict: 配置字典
198
+ """
199
+ return self.dict()
200
+
201
+ def copy_with_updates(self, **updates) -> "AuthConfig":
202
+ """
203
+ 创建配置副本并更新指定字段
204
+
205
+ Args:
206
+ **updates: 要更新的字段
207
+
208
+ Returns:
209
+ AuthConfig: 更新后的配置副本
53
210
  """
54
- required_fields = ['agenterra_iam_host', 'server_name', 'access_key']
55
- for field in required_fields:
56
- if not getattr(self, field):
57
- raise ValueError(f"配置项 {field} 不能为空")
58
- return True
211
+ config_dict = self.dict()
212
+ config_dict.update(updates)
213
+ return AuthConfig(**config_dict)
59
214
 
60
215
  def _normalize_path(self, path: str) -> str:
61
216
  """
@@ -83,6 +83,53 @@ class ConnectAgenterraIam(object):
83
83
  # 标记为已初始化
84
84
  self._initialized = True
85
85
 
86
+ @classmethod
87
+ def get_instance(cls):
88
+ """
89
+ 获取已初始化的ConnectAgenterraIam单例实例
90
+
91
+ 如果实例尚未初始化,会抛出异常提示用户先进行配置
92
+ 这个方法通常在用户已经通过setup_auth()进行配置后使用
93
+
94
+ 返回:
95
+ - ConnectAgenterraIam: 已初始化的单例实例
96
+
97
+ 异常:
98
+ - RuntimeError: 当实例未初始化时抛出
99
+ """
100
+ if cls._instance is None or not cls._initialized:
101
+ raise RuntimeError(
102
+ "ConnectAgenterraIam实例尚未初始化。请先使用以下方式之一进行配置:\n"
103
+ "1. 使用setup_auth()进行一键配置\n"
104
+ "2. 手动创建ConnectAgenterraIam实例:ConnectAgenterraIam(config=your_config)"
105
+ )
106
+ return cls._instance
107
+
108
+ @classmethod
109
+ def get_instance_lazy(cls):
110
+ """
111
+ 获取已初始化的单例实例(延迟模式)
112
+
113
+ 此方法支持延迟初始化,当实例未初始化时返回 None 而不是抛出异常。
114
+ 适用于在模块导入时需要获取实例但可能尚未初始化的场景。
115
+
116
+ 返回:
117
+ - ConnectAgenterraIam: 已初始化的单例实例,如果未初始化则返回 None
118
+ """
119
+ if cls._instance is None or not cls._initialized:
120
+ return None
121
+ return cls._instance
122
+
123
+ @classmethod
124
+ def is_initialized(cls):
125
+ """
126
+ 检查单例实例是否已初始化
127
+
128
+ 返回:
129
+ - bool: 如果实例已初始化返回 True,否则返回 False
130
+ """
131
+ return cls._instance is not None and cls._initialized
132
+
86
133
  def reload_config(self, config):
87
134
  """
88
135
  重新加载配置
@@ -760,6 +807,168 @@ class ConnectAgenterraIam(object):
760
807
  self.logger.error(f"[{method_name}] 异常堆栈: {traceback.format_exc()}")
761
808
  return False
762
809
 
810
+ def add_custom_config(self, user_id, config_name, config_value=None):
811
+ """
812
+ 机机接口:添加用户自定义配置
813
+
814
+ 为指定用户添加或更新自定义属性配置。
815
+
816
+ 参数:
817
+ - user_id: 用户ID
818
+ - config_name: 配置项名称
819
+ - config_value: 配置项值(可选)
820
+
821
+ 返回:
822
+ - 成功: 返回响应对象
823
+ - 失败: 返回False
824
+ """
825
+ method_name = "add_custom_config"
826
+ self.logger.info(f"[{method_name}] 开始添加用户自定义配置 - user_id: {user_id}, config_name: {config_name}")
827
+
828
+ try:
829
+ body = {
830
+ "server_name": self.server_name,
831
+ "access_key": self.access_key,
832
+ "user_id": user_id,
833
+ "config_name": config_name
834
+ }
835
+
836
+ # 添加可选参数
837
+ if config_value is not None:
838
+ body["config_value"] = config_value
839
+
840
+ uri = "/api/v2/service/add_custom_config"
841
+ url = self.agenterra_iam_host + uri
842
+
843
+ # 记录请求信息
844
+ self._log_request(method_name, url, self.headers, body)
845
+
846
+ response = requests.post(
847
+ url=url,
848
+ headers=self.headers,
849
+ json=body,
850
+ verify=False
851
+ )
852
+
853
+ # 记录响应信息
854
+ self._log_response(method_name, response)
855
+
856
+ if response.status_code == 200:
857
+ self.logger.info(f"[{method_name}] 添加用户自定义配置成功")
858
+ return response
859
+ else:
860
+ self.logger.warning(f"[{method_name}] 添加用户自定义配置失败 - 状态码: {response.status_code}")
861
+
862
+ return False
863
+ except Exception as e:
864
+ self.logger.error(f"[{method_name}] 添加用户自定义配置请求异常: {str(e)}")
865
+ self.logger.error(f"[{method_name}] 异常堆栈: {traceback.format_exc()}")
866
+ return False
867
+
868
+ def get_custom_configs(self, user_id):
869
+ """
870
+ 机机接口:获取用户自定义配置
871
+
872
+ 获取指定用户的所有自定义属性配置。
873
+
874
+ 参数:
875
+ - user_id: 用户ID
876
+
877
+ 返回:
878
+ - 成功: 返回响应对象
879
+ - 失败: 返回False
880
+ """
881
+ method_name = "get_custom_configs"
882
+ self.logger.info(f"[{method_name}] 开始获取用户自定义配置 - user_id: {user_id}")
883
+
884
+ try:
885
+ body = {
886
+ "server_name": self.server_name,
887
+ "access_key": self.access_key,
888
+ "user_id": user_id
889
+ }
890
+
891
+ uri = "/api/v2/service/get_custom_configs"
892
+ url = self.agenterra_iam_host + uri
893
+
894
+ # 记录请求信息
895
+ self._log_request(method_name, url, self.headers, body)
896
+
897
+ response = requests.post(
898
+ url=url,
899
+ headers=self.headers,
900
+ json=body,
901
+ verify=False
902
+ )
903
+
904
+ # 记录响应信息
905
+ self._log_response(method_name, response)
906
+
907
+ if response.status_code == 200:
908
+ self.logger.info(f"[{method_name}] 获取用户自定义配置成功")
909
+ return response
910
+ else:
911
+ self.logger.warning(f"[{method_name}] 获取用户自定义配置失败 - 状态码: {response.status_code}")
912
+
913
+ return False
914
+ except Exception as e:
915
+ self.logger.error(f"[{method_name}] 获取用户自定义配置请求异常: {str(e)}")
916
+ self.logger.error(f"[{method_name}] 异常堆栈: {traceback.format_exc()}")
917
+ return False
918
+
919
+ def delete_custom_config(self, user_id, config_name):
920
+ """
921
+ 机机接口:删除用户自定义配置
922
+
923
+ 删除指定用户的指定自定义属性配置。
924
+
925
+ 参数:
926
+ - user_id: 用户ID
927
+ - config_name: 配置项名称
928
+
929
+ 返回:
930
+ - 成功: 返回响应对象
931
+ - 失败: 返回False
932
+ """
933
+ method_name = "delete_custom_config"
934
+ self.logger.info(f"[{method_name}] 开始删除用户自定义配置 - user_id: {user_id}, config_name: {config_name}")
935
+
936
+ try:
937
+ body = {
938
+ "server_name": self.server_name,
939
+ "access_key": self.access_key,
940
+ "user_id": user_id,
941
+ "config_name": config_name
942
+ }
943
+
944
+ uri = "/api/v2/service/delete_custom_config"
945
+ url = self.agenterra_iam_host + uri
946
+
947
+ # 记录请求信息
948
+ self._log_request(method_name, url, self.headers, body)
949
+
950
+ response = requests.post(
951
+ url=url,
952
+ headers=self.headers,
953
+ json=body,
954
+ verify=False
955
+ )
956
+
957
+ # 记录响应信息
958
+ self._log_response(method_name, response)
959
+
960
+ if response.status_code == 200:
961
+ self.logger.info(f"[{method_name}] 删除用户自定义配置成功")
962
+ return response
963
+ else:
964
+ self.logger.warning(f"[{method_name}] 删除用户自定义配置失败 - 状态码: {response.status_code}")
965
+
966
+ return False
967
+ except Exception as e:
968
+ self.logger.error(f"[{method_name}] 删除用户自定义配置请求异常: {str(e)}")
969
+ self.logger.error(f"[{method_name}] 异常堆栈: {traceback.format_exc()}")
970
+ return False
971
+
763
972
  def merge_credential(self, target_user_id, cred_type, cred_value, merge_reason=None):
764
973
  """
765
974
  机机接口:凭证合并