kstlib 0.0.1a0__py3-none-any.whl → 1.0.0__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.
Files changed (166) hide show
  1. kstlib/__init__.py +266 -1
  2. kstlib/__main__.py +16 -0
  3. kstlib/alerts/__init__.py +110 -0
  4. kstlib/alerts/channels/__init__.py +36 -0
  5. kstlib/alerts/channels/base.py +197 -0
  6. kstlib/alerts/channels/email.py +227 -0
  7. kstlib/alerts/channels/slack.py +389 -0
  8. kstlib/alerts/exceptions.py +72 -0
  9. kstlib/alerts/manager.py +651 -0
  10. kstlib/alerts/models.py +142 -0
  11. kstlib/alerts/throttle.py +263 -0
  12. kstlib/auth/__init__.py +139 -0
  13. kstlib/auth/callback.py +399 -0
  14. kstlib/auth/config.py +502 -0
  15. kstlib/auth/errors.py +127 -0
  16. kstlib/auth/models.py +316 -0
  17. kstlib/auth/providers/__init__.py +14 -0
  18. kstlib/auth/providers/base.py +393 -0
  19. kstlib/auth/providers/oauth2.py +645 -0
  20. kstlib/auth/providers/oidc.py +821 -0
  21. kstlib/auth/session.py +338 -0
  22. kstlib/auth/token.py +482 -0
  23. kstlib/cache/__init__.py +50 -0
  24. kstlib/cache/decorator.py +261 -0
  25. kstlib/cache/strategies.py +516 -0
  26. kstlib/cli/__init__.py +8 -0
  27. kstlib/cli/app.py +195 -0
  28. kstlib/cli/commands/__init__.py +5 -0
  29. kstlib/cli/commands/auth/__init__.py +39 -0
  30. kstlib/cli/commands/auth/common.py +122 -0
  31. kstlib/cli/commands/auth/login.py +325 -0
  32. kstlib/cli/commands/auth/logout.py +74 -0
  33. kstlib/cli/commands/auth/providers.py +57 -0
  34. kstlib/cli/commands/auth/status.py +291 -0
  35. kstlib/cli/commands/auth/token.py +199 -0
  36. kstlib/cli/commands/auth/whoami.py +106 -0
  37. kstlib/cli/commands/config.py +89 -0
  38. kstlib/cli/commands/ops/__init__.py +39 -0
  39. kstlib/cli/commands/ops/attach.py +49 -0
  40. kstlib/cli/commands/ops/common.py +269 -0
  41. kstlib/cli/commands/ops/list_sessions.py +252 -0
  42. kstlib/cli/commands/ops/logs.py +49 -0
  43. kstlib/cli/commands/ops/start.py +98 -0
  44. kstlib/cli/commands/ops/status.py +138 -0
  45. kstlib/cli/commands/ops/stop.py +60 -0
  46. kstlib/cli/commands/rapi/__init__.py +60 -0
  47. kstlib/cli/commands/rapi/call.py +341 -0
  48. kstlib/cli/commands/rapi/list.py +99 -0
  49. kstlib/cli/commands/rapi/show.py +206 -0
  50. kstlib/cli/commands/secrets/__init__.py +35 -0
  51. kstlib/cli/commands/secrets/common.py +425 -0
  52. kstlib/cli/commands/secrets/decrypt.py +88 -0
  53. kstlib/cli/commands/secrets/doctor.py +743 -0
  54. kstlib/cli/commands/secrets/encrypt.py +242 -0
  55. kstlib/cli/commands/secrets/shred.py +96 -0
  56. kstlib/cli/common.py +86 -0
  57. kstlib/config/__init__.py +76 -0
  58. kstlib/config/exceptions.py +110 -0
  59. kstlib/config/export.py +225 -0
  60. kstlib/config/loader.py +963 -0
  61. kstlib/config/sops.py +287 -0
  62. kstlib/db/__init__.py +54 -0
  63. kstlib/db/aiosqlcipher.py +137 -0
  64. kstlib/db/cipher.py +112 -0
  65. kstlib/db/database.py +367 -0
  66. kstlib/db/exceptions.py +25 -0
  67. kstlib/db/pool.py +302 -0
  68. kstlib/helpers/__init__.py +35 -0
  69. kstlib/helpers/exceptions.py +11 -0
  70. kstlib/helpers/time_trigger.py +396 -0
  71. kstlib/kstlib.conf.yml +890 -0
  72. kstlib/limits.py +963 -0
  73. kstlib/logging/__init__.py +108 -0
  74. kstlib/logging/manager.py +633 -0
  75. kstlib/mail/__init__.py +42 -0
  76. kstlib/mail/builder.py +626 -0
  77. kstlib/mail/exceptions.py +27 -0
  78. kstlib/mail/filesystem.py +248 -0
  79. kstlib/mail/transport.py +224 -0
  80. kstlib/mail/transports/__init__.py +19 -0
  81. kstlib/mail/transports/gmail.py +268 -0
  82. kstlib/mail/transports/resend.py +324 -0
  83. kstlib/mail/transports/smtp.py +326 -0
  84. kstlib/meta.py +72 -0
  85. kstlib/metrics/__init__.py +88 -0
  86. kstlib/metrics/decorators.py +1090 -0
  87. kstlib/metrics/exceptions.py +14 -0
  88. kstlib/monitoring/__init__.py +116 -0
  89. kstlib/monitoring/_styles.py +163 -0
  90. kstlib/monitoring/cell.py +57 -0
  91. kstlib/monitoring/config.py +424 -0
  92. kstlib/monitoring/delivery.py +579 -0
  93. kstlib/monitoring/exceptions.py +63 -0
  94. kstlib/monitoring/image.py +220 -0
  95. kstlib/monitoring/kv.py +79 -0
  96. kstlib/monitoring/list.py +69 -0
  97. kstlib/monitoring/metric.py +88 -0
  98. kstlib/monitoring/monitoring.py +341 -0
  99. kstlib/monitoring/renderer.py +139 -0
  100. kstlib/monitoring/service.py +392 -0
  101. kstlib/monitoring/table.py +129 -0
  102. kstlib/monitoring/types.py +56 -0
  103. kstlib/ops/__init__.py +86 -0
  104. kstlib/ops/base.py +148 -0
  105. kstlib/ops/container.py +577 -0
  106. kstlib/ops/exceptions.py +209 -0
  107. kstlib/ops/manager.py +407 -0
  108. kstlib/ops/models.py +176 -0
  109. kstlib/ops/tmux.py +372 -0
  110. kstlib/ops/validators.py +287 -0
  111. kstlib/py.typed +0 -0
  112. kstlib/rapi/__init__.py +118 -0
  113. kstlib/rapi/client.py +875 -0
  114. kstlib/rapi/config.py +861 -0
  115. kstlib/rapi/credentials.py +887 -0
  116. kstlib/rapi/exceptions.py +213 -0
  117. kstlib/resilience/__init__.py +101 -0
  118. kstlib/resilience/circuit_breaker.py +440 -0
  119. kstlib/resilience/exceptions.py +95 -0
  120. kstlib/resilience/heartbeat.py +491 -0
  121. kstlib/resilience/rate_limiter.py +506 -0
  122. kstlib/resilience/shutdown.py +417 -0
  123. kstlib/resilience/watchdog.py +637 -0
  124. kstlib/secrets/__init__.py +29 -0
  125. kstlib/secrets/exceptions.py +19 -0
  126. kstlib/secrets/models.py +62 -0
  127. kstlib/secrets/providers/__init__.py +79 -0
  128. kstlib/secrets/providers/base.py +58 -0
  129. kstlib/secrets/providers/environment.py +66 -0
  130. kstlib/secrets/providers/keyring.py +107 -0
  131. kstlib/secrets/providers/kms.py +223 -0
  132. kstlib/secrets/providers/kwargs.py +101 -0
  133. kstlib/secrets/providers/sops.py +209 -0
  134. kstlib/secrets/resolver.py +221 -0
  135. kstlib/secrets/sensitive.py +130 -0
  136. kstlib/secure/__init__.py +23 -0
  137. kstlib/secure/fs.py +194 -0
  138. kstlib/secure/permissions.py +70 -0
  139. kstlib/ssl.py +347 -0
  140. kstlib/ui/__init__.py +23 -0
  141. kstlib/ui/exceptions.py +26 -0
  142. kstlib/ui/panels.py +484 -0
  143. kstlib/ui/spinner.py +864 -0
  144. kstlib/ui/tables.py +382 -0
  145. kstlib/utils/__init__.py +48 -0
  146. kstlib/utils/dict.py +36 -0
  147. kstlib/utils/formatting.py +338 -0
  148. kstlib/utils/http_trace.py +237 -0
  149. kstlib/utils/lazy.py +49 -0
  150. kstlib/utils/secure_delete.py +205 -0
  151. kstlib/utils/serialization.py +247 -0
  152. kstlib/utils/text.py +56 -0
  153. kstlib/utils/validators.py +124 -0
  154. kstlib/websocket/__init__.py +97 -0
  155. kstlib/websocket/exceptions.py +214 -0
  156. kstlib/websocket/manager.py +1102 -0
  157. kstlib/websocket/models.py +361 -0
  158. kstlib-1.0.0.dist-info/METADATA +201 -0
  159. kstlib-1.0.0.dist-info/RECORD +163 -0
  160. {kstlib-0.0.1a0.dist-info → kstlib-1.0.0.dist-info}/WHEEL +1 -1
  161. kstlib-1.0.0.dist-info/entry_points.txt +2 -0
  162. kstlib-1.0.0.dist-info/licenses/LICENSE.md +9 -0
  163. kstlib-0.0.1a0.dist-info/METADATA +0 -29
  164. kstlib-0.0.1a0.dist-info/RECORD +0 -6
  165. kstlib-0.0.1a0.dist-info/licenses/LICENSE.md +0 -5
  166. {kstlib-0.0.1a0.dist-info → kstlib-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,287 @@
1
+ """Input validation for kstlib.ops module.
2
+
3
+ This module provides validation functions for all user-exposed values
4
+ in the ops module, implementing deep defense against malformed or
5
+ malicious input.
6
+
7
+ Hard limits are enforced to prevent resource exhaustion and ensure
8
+ predictable behavior across all backends.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+
15
+ # ============================================================================
16
+ # Constants - Hard Limits
17
+ # ============================================================================
18
+
19
+ # Session name limits
20
+ MAX_SESSION_NAME_LENGTH = 64
21
+ SESSION_NAME_PATTERN = re.compile(r"^[a-zA-Z][a-zA-Z0-9_-]*$")
22
+
23
+ # Container image limits
24
+ MAX_IMAGE_NAME_LENGTH = 256
25
+ # OCI image name: registry/path:tag@digest format
26
+ IMAGE_NAME_PATTERN = re.compile(
27
+ r"^[a-z0-9]([a-z0-9._-]*[a-z0-9])?(/[a-z0-9]([a-z0-9._-]*[a-z0-9])?)*"
28
+ r"(:[a-zA-Z0-9._-]+)?(@sha256:[a-f0-9]{64})?$"
29
+ )
30
+
31
+ # Volume limits
32
+ MAX_VOLUMES = 20
33
+ VOLUME_PATTERN = re.compile(r"^[^:]+:[^:]+(:ro|:rw)?$")
34
+
35
+ # Port limits
36
+ MAX_PORTS = 50
37
+ PORT_PATTERN = re.compile(r"^(\d{1,5}:)?\d{1,5}(/tcp|/udp)?$")
38
+
39
+ # Environment variable limits
40
+ MAX_ENV_VARS = 100
41
+ MAX_ENV_KEY_LENGTH = 128
42
+ MAX_ENV_VALUE_LENGTH = 32768 # 32KB
43
+
44
+ # Command limits
45
+ MAX_COMMAND_LENGTH = 4096
46
+
47
+ # Dangerous patterns to block in commands
48
+ DANGEROUS_PATTERNS = [
49
+ r";\s*rm\s+-rf", # rm -rf after semicolon
50
+ r"\$\(.*\)", # Command substitution
51
+ r"`.*`", # Backtick substitution
52
+ r"\|\s*sh\b", # Pipe to shell
53
+ r"\|\s*bash\b", # Pipe to bash
54
+ ]
55
+
56
+
57
+ # ============================================================================
58
+ # Validation Functions
59
+ # ============================================================================
60
+
61
+
62
+ def validate_session_name(name: str) -> str:
63
+ """Validate and return session name.
64
+
65
+ Rules:
66
+ - Cannot be empty
67
+ - Max 64 characters (hard limit)
68
+ - Must start with letter
69
+ - Only alphanumeric, underscore, hyphen allowed
70
+ - No shell metacharacters
71
+
72
+ Args:
73
+ name: Session name to validate.
74
+
75
+ Returns:
76
+ The validated session name (unchanged).
77
+
78
+ Raises:
79
+ ValueError: If name is invalid.
80
+
81
+ Examples:
82
+ >>> validate_session_name("mybot")
83
+ 'mybot'
84
+ >>> validate_session_name("my-bot_01")
85
+ 'my-bot_01'
86
+ >>> validate_session_name("")
87
+ Traceback (most recent call last):
88
+ ...
89
+ ValueError: Session name cannot be empty
90
+ """
91
+ if not name:
92
+ raise ValueError("Session name cannot be empty")
93
+ if len(name) > MAX_SESSION_NAME_LENGTH:
94
+ raise ValueError(f"Session name too long (max {MAX_SESSION_NAME_LENGTH} chars)")
95
+ if not SESSION_NAME_PATTERN.match(name):
96
+ raise ValueError(
97
+ "Session name must start with letter and contain only alphanumeric, underscore, or hyphen characters"
98
+ )
99
+ return name
100
+
101
+
102
+ def validate_image_name(image: str) -> str:
103
+ """Validate container image name.
104
+
105
+ Rules:
106
+ - Max 256 characters
107
+ - Valid Docker/OCI image format
108
+ - No shell injection characters
109
+
110
+ Args:
111
+ image: Container image name to validate.
112
+
113
+ Returns:
114
+ The validated image name (unchanged).
115
+
116
+ Raises:
117
+ ValueError: If image name is invalid.
118
+
119
+ Examples:
120
+ >>> validate_image_name("python:3.10-slim")
121
+ 'python:3.10-slim'
122
+ >>> validate_image_name("registry.io/path/image:tag")
123
+ 'registry.io/path/image:tag'
124
+ """
125
+ if not image:
126
+ raise ValueError("Image name cannot be empty")
127
+ if len(image) > MAX_IMAGE_NAME_LENGTH:
128
+ raise ValueError(f"Image name too long (max {MAX_IMAGE_NAME_LENGTH} chars)")
129
+ if not IMAGE_NAME_PATTERN.match(image):
130
+ raise ValueError(f"Invalid image name format: {image}")
131
+ return image
132
+
133
+
134
+ def validate_volumes(volumes: list[str]) -> list[str]:
135
+ """Validate volume mappings.
136
+
137
+ Rules:
138
+ - Max 20 volumes
139
+ - Valid format: host:container[:ro|:rw]
140
+ - No path traversal (..)
141
+
142
+ Args:
143
+ volumes: List of volume mappings to validate.
144
+
145
+ Returns:
146
+ The validated volumes list (unchanged).
147
+
148
+ Raises:
149
+ ValueError: If volumes are invalid.
150
+
151
+ Examples:
152
+ >>> validate_volumes(["./data:/app/data"])
153
+ ['./data:/app/data']
154
+ >>> validate_volumes(["./logs:/app/logs:ro"])
155
+ ['./logs:/app/logs:ro']
156
+ """
157
+ if len(volumes) > MAX_VOLUMES:
158
+ raise ValueError(f"Too many volumes (max {MAX_VOLUMES})")
159
+ for vol in volumes:
160
+ if not VOLUME_PATTERN.match(vol):
161
+ raise ValueError(f"Invalid volume format: {vol}")
162
+ if ".." in vol:
163
+ raise ValueError(f"Path traversal not allowed in volume: {vol}")
164
+ return volumes
165
+
166
+
167
+ def validate_ports(ports: list[str]) -> list[str]:
168
+ """Validate port mappings.
169
+
170
+ Rules:
171
+ - Max 50 ports
172
+ - Valid format: [host:]container[/tcp|/udp]
173
+ - Port numbers in range 1-65535
174
+
175
+ Args:
176
+ ports: List of port mappings to validate.
177
+
178
+ Returns:
179
+ The validated ports list (unchanged).
180
+
181
+ Raises:
182
+ ValueError: If ports are invalid.
183
+
184
+ Examples:
185
+ >>> validate_ports(["8080:80"])
186
+ ['8080:80']
187
+ >>> validate_ports(["8080"])
188
+ ['8080']
189
+ >>> validate_ports(["8080:80/tcp"])
190
+ ['8080:80/tcp']
191
+ """
192
+ if len(ports) > MAX_PORTS:
193
+ raise ValueError(f"Too many ports (max {MAX_PORTS})")
194
+ for port in ports:
195
+ if not PORT_PATTERN.match(port):
196
+ raise ValueError(f"Invalid port format: {port}")
197
+ # Validate port numbers are in valid range
198
+ nums = re.findall(r"\d+", port)
199
+ for num in nums:
200
+ port_num = int(num)
201
+ if port_num < 1 or port_num > 65535:
202
+ raise ValueError(f"Port number out of range (1-65535): {num}")
203
+ return ports
204
+
205
+
206
+ def validate_env(env: dict[str, str]) -> dict[str, str]:
207
+ """Validate environment variables.
208
+
209
+ Rules:
210
+ - Max 100 env vars
211
+ - Key max 128 characters
212
+ - Value max 32KB
213
+ - Key must be valid identifier
214
+
215
+ Args:
216
+ env: Dictionary of environment variables to validate.
217
+
218
+ Returns:
219
+ The validated env dict (unchanged).
220
+
221
+ Raises:
222
+ ValueError: If env vars are invalid.
223
+
224
+ Examples:
225
+ >>> validate_env({"APP_ENV": "production"})
226
+ {'APP_ENV': 'production'}
227
+ """
228
+ if len(env) > MAX_ENV_VARS:
229
+ raise ValueError(f"Too many env vars (max {MAX_ENV_VARS})")
230
+ for key, value in env.items():
231
+ if len(key) > MAX_ENV_KEY_LENGTH:
232
+ raise ValueError(f"Env key too long (max {MAX_ENV_KEY_LENGTH}): {key[:20]}...")
233
+ if len(value) > MAX_ENV_VALUE_LENGTH:
234
+ raise ValueError(f"Env value too long (max {MAX_ENV_VALUE_LENGTH}) for key: {key}")
235
+ if not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", key):
236
+ raise ValueError(f"Invalid env key format: {key}")
237
+ return env
238
+
239
+
240
+ def validate_command(command: str | None) -> str | None:
241
+ """Validate command string.
242
+
243
+ Rules:
244
+ - Max 4096 characters
245
+ - No dangerous shell patterns
246
+
247
+ Args:
248
+ command: Command string to validate (can be None).
249
+
250
+ Returns:
251
+ The validated command (unchanged).
252
+
253
+ Raises:
254
+ ValueError: If command is invalid or contains dangerous patterns.
255
+
256
+ Examples:
257
+ >>> validate_command("python -m app")
258
+ 'python -m app'
259
+ >>> validate_command(None) is None
260
+ True
261
+ """
262
+ if command is None:
263
+ return None
264
+ if len(command) > MAX_COMMAND_LENGTH:
265
+ raise ValueError(f"Command too long (max {MAX_COMMAND_LENGTH} chars)")
266
+ for pattern in DANGEROUS_PATTERNS:
267
+ if re.search(pattern, command, re.IGNORECASE):
268
+ raise ValueError("Potentially dangerous command pattern detected")
269
+ return command
270
+
271
+
272
+ __all__ = [
273
+ "MAX_COMMAND_LENGTH",
274
+ "MAX_ENV_KEY_LENGTH",
275
+ "MAX_ENV_VALUE_LENGTH",
276
+ "MAX_ENV_VARS",
277
+ "MAX_IMAGE_NAME_LENGTH",
278
+ "MAX_PORTS",
279
+ "MAX_SESSION_NAME_LENGTH",
280
+ "MAX_VOLUMES",
281
+ "validate_command",
282
+ "validate_env",
283
+ "validate_image_name",
284
+ "validate_ports",
285
+ "validate_session_name",
286
+ "validate_volumes",
287
+ ]
kstlib/py.typed ADDED
File without changes
@@ -0,0 +1,118 @@
1
+ """REST API wrapper module (config-driven).
2
+
3
+ This module provides a config-driven REST API client with multi-source
4
+ credential resolution and detailed logging.
5
+
6
+ Features:
7
+ - Config-driven endpoints from kstlib.conf.yml or external *.rapi.yml files
8
+ - Auto-discovery of *.rapi.yml files in current directory
9
+ - Multi-source credentials (env, file, sops, provider)
10
+ - Header merging at three levels (service, endpoint, runtime)
11
+ - Automatic retry with exponential backoff
12
+ - TRACE-level logging for debugging
13
+ - Hard limits with deep defense
14
+
15
+ Quick Start:
16
+ >>> from kstlib.rapi import call
17
+ >>> response = call("httpbin.get_ip") # doctest: +SKIP
18
+ >>> response.data # doctest: +SKIP
19
+ {'origin': '...'}
20
+
21
+ With Client Instance:
22
+ >>> from kstlib.rapi import RapiClient
23
+ >>> client = RapiClient() # doctest: +SKIP
24
+ >>> response = client.call("httpbin.post_data", body={"key": "value"}) # doctest: +SKIP
25
+
26
+ From External YAML File:
27
+ >>> from kstlib.rapi import RapiClient
28
+ >>> client = RapiClient.from_file("github.rapi.yml") # doctest: +SKIP
29
+ >>> response = client.call("github.user") # doctest: +SKIP
30
+
31
+ Auto-Discovery:
32
+ >>> from kstlib.rapi import RapiClient
33
+ >>> client = RapiClient.discover() # Finds *.rapi.yml in cwd # doctest: +SKIP
34
+ >>> client.list_apis() # doctest: +SKIP
35
+ ['github', 'slack']
36
+
37
+ Async:
38
+ >>> from kstlib.rapi import call_async
39
+ >>> response = await call_async("httpbin.get_ip") # doctest: +SKIP
40
+
41
+ Configuration:
42
+ Configure endpoints in kstlib.conf.yml:
43
+
44
+ .. code-block:: yaml
45
+
46
+ rapi:
47
+ limits:
48
+ timeout: 30
49
+ max_response_size: "10M"
50
+ max_retries: 3
51
+
52
+ api:
53
+ httpbin:
54
+ base_url: "https://httpbin.org"
55
+ endpoints:
56
+ get_ip:
57
+ path: "/ip"
58
+ post_data:
59
+ path: "/post"
60
+ method: POST
61
+
62
+ Or use external *.rapi.yml files (simplified format):
63
+
64
+ .. code-block:: yaml
65
+
66
+ # github.rapi.yml
67
+ name: github
68
+ base_url: "https://api.github.com"
69
+ credentials:
70
+ type: sops
71
+ path: "./tokens/github.sops.json"
72
+ token_path: ".access_token"
73
+ auth:
74
+ type: bearer
75
+ endpoints:
76
+ user:
77
+ path: "/user"
78
+ repos:
79
+ path: "/user/repos"
80
+ """
81
+
82
+ from kstlib.rapi.client import RapiClient, RapiResponse, call, call_async
83
+ from kstlib.rapi.config import (
84
+ ApiConfig,
85
+ EndpointConfig,
86
+ HmacConfig,
87
+ RapiConfigManager,
88
+ load_rapi_config,
89
+ )
90
+ from kstlib.rapi.credentials import CredentialRecord, CredentialResolver
91
+ from kstlib.rapi.exceptions import (
92
+ CredentialError,
93
+ EndpointAmbiguousError,
94
+ EndpointNotFoundError,
95
+ RapiError,
96
+ RequestError,
97
+ ResponseTooLargeError,
98
+ )
99
+
100
+ __all__ = [
101
+ "ApiConfig",
102
+ "CredentialError",
103
+ "CredentialRecord",
104
+ "CredentialResolver",
105
+ "EndpointAmbiguousError",
106
+ "EndpointConfig",
107
+ "EndpointNotFoundError",
108
+ "HmacConfig",
109
+ "RapiClient",
110
+ "RapiConfigManager",
111
+ "RapiError",
112
+ "RapiResponse",
113
+ "RequestError",
114
+ "ResponseTooLargeError",
115
+ "call",
116
+ "call_async",
117
+ "load_rapi_config",
118
+ ]