infrahub-testcontainers 1.3.0b3__tar.gz → 1.4.13__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 (16) hide show
  1. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/PKG-INFO +2 -1
  2. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/infrahub_testcontainers/container.py +19 -2
  3. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/infrahub_testcontainers/docker-compose-cluster.test.yml +60 -2
  4. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/infrahub_testcontainers/docker-compose.test.yml +58 -2
  5. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/infrahub_testcontainers/helpers.py +4 -1
  6. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/pyproject.toml +4 -3
  7. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/README.md +0 -0
  8. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/infrahub_testcontainers/__init__.py +0 -0
  9. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/infrahub_testcontainers/constants.py +0 -0
  10. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/infrahub_testcontainers/haproxy.cfg +0 -0
  11. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/infrahub_testcontainers/host.py +0 -0
  12. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/infrahub_testcontainers/measurements.py +0 -0
  13. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/infrahub_testcontainers/models.py +0 -0
  14. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/infrahub_testcontainers/performance_test.py +0 -0
  15. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/infrahub_testcontainers/plugin.py +0 -0
  16. {infrahub_testcontainers-1.3.0b3 → infrahub_testcontainers-1.4.13}/infrahub_testcontainers/prometheus.yml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: infrahub-testcontainers
3
- Version: 1.3.0b3
3
+ Version: 1.4.13
4
4
  Summary: Testcontainers instance for Infrahub to easily build integration tests
5
5
  License: Apache-2.0
6
6
  Author: OpsMill
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Programming Language :: Python :: 3.13
17
17
  Requires-Dist: httpx (>=0.28.1,<0.29.0)
18
+ Requires-Dist: prefect-client (==3.4.13)
18
19
  Requires-Dist: psutil
19
20
  Requires-Dist: pydantic (>=2.10.6,<3.0.0)
20
21
  Requires-Dist: pytest
@@ -29,7 +29,7 @@ INFRAHUB_SERVICES: dict[str, ContainerService] = {
29
29
 
30
30
  PROJECT_ENV_VARIABLES: dict[str, str] = {
31
31
  "MESSAGE_QUEUE_DOCKER_IMAGE": "rabbitmq:3.13.7-management",
32
- "CACHE_DOCKER_IMAGE": "redis:7.2.4",
32
+ "CACHE_DOCKER_IMAGE": "redis:7.2.11",
33
33
  "INFRAHUB_TESTING_DOCKER_IMAGE": "registry.opsmill.io/opsmill/infrahub",
34
34
  "INFRAHUB_TESTING_DOCKER_ENTRYPOINT": f"gunicorn --config backend/infrahub/serve/gunicorn_config.py -w {os.environ.get('INFRAHUB_TESTING_WEB_CONCURRENCY', 4)} --logger-class infrahub.serve.log.GunicornLogger infrahub.server:app", # noqa: E501
35
35
  "INFRAHUB_TESTING_IMAGE_VERSION": infrahub_version,
@@ -59,6 +59,8 @@ PROJECT_ENV_VARIABLES: dict[str, str] = {
59
59
  "INFRAHUB_TESTING_PREFECT_UI_ENABLED": "true",
60
60
  "INFRAHUB_TESTING_DOCKER_PULL": "true",
61
61
  "INFRAHUB_TESTING_SCHEMA_STRICT_MODE": "true",
62
+ "INFRAHUB_TESTING_TASKMGR_API_WORKERS": "1",
63
+ "INFRAHUB_TESTING_TASKMGR_BACKGROUND_SVC_REPLICAS": "0",
62
64
  }
63
65
 
64
66
 
@@ -141,7 +143,22 @@ class InfrahubDockerCompose(DockerCompose):
141
143
  "INFRAHUB_TESTING_DOCKER_ENTRYPOINT": f"gunicorn --config community/backend/infrahub/serve/gunicorn_config.py -w {os.environ.get('INFRAHUB_TESTING_WEB_CONCURRENCY', 4)} --logger-class infrahub.serve.log.GunicornLogger infrahub_enterprise.server:app", # noqa: E501
142
144
  "INFRAHUB_TESTING_WORKFLOW_DEFAULT_WORKER_TYPE": "infrahubentasync",
143
145
  "INFRAHUB_TESTING_PREFECT_UI_ENABLED": "false",
144
- "NEO4J_DOCKER_IMAGE": "neo4j:5.20.0-enterprise",
146
+ "NEO4J_DOCKER_IMAGE": "neo4j:2025.03.0-enterprise",
147
+ }
148
+ )
149
+ if os.environ.get("INFRAHUB_TESTING_TASKMGR_SCALEOUT"):
150
+ PROJECT_ENV_VARIABLES.update(
151
+ {
152
+ "INFRAHUB_TESTING_TASKMGR_BACKGROUND_SVC_REPLICAS": "1",
153
+ "PREFECT_MESSAGING_BROKER": "prefect_redis.messaging",
154
+ "PREFECT_MESSAGING_CACHE": "prefect_redis.messaging",
155
+ "PREFECT__SERVER_WEBSERVER_ONLY": "true",
156
+ "PREFECT_API_DATABASE_MIGRATE_ON_START": "false",
157
+ "PREFECT_API_BLOCKS_REGISTER_ON_START": "false",
158
+ "PREFECT_SERVER_SERVICES_EVENT_LOGGER_ENABLED": "false",
159
+ "PREFECT_SERVER_SERVICES_EVENT_PERSISTER_ENABLED": "false",
160
+ "PREFECT_SERVER_SERVICES_TRIGGERS_ENABLED": "false",
161
+ "PREFECT_SERVER_SERVICES_TASK_RUN_RECORDER_ENABLED": "false",
145
162
  }
146
163
  )
147
164
 
@@ -37,7 +37,7 @@ services:
37
37
  - ${INFRAHUB_TESTING_MESSAGE_QUEUE_PORT:-0}:15692
38
38
 
39
39
  cache:
40
- image: ${CACHE_DOCKER_IMAGE:-redis:7.2.4}
40
+ image: ${CACHE_DOCKER_IMAGE:-redis:7.2.11}
41
41
  restart: unless-stopped
42
42
  healthcheck:
43
43
  test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
@@ -91,6 +91,7 @@ services:
91
91
  start_period: 3s
92
92
  ports:
93
93
  - ${INFRAHUB_TESTING_DATABASE_PORT:-0}:6362
94
+ - ${INFRAHUB_TESTING_DATABASE_BOLT_PORT:-0}:7687
94
95
  - ${INFRAHUB_TESTING_DATABASE_UI_PORT:-0}:7474
95
96
 
96
97
  database-core2:
@@ -126,6 +127,7 @@ services:
126
127
  com.github.job: "${JOB_NAME:-unknown}"
127
128
  ports:
128
129
  - "${INFRAHUB_TESTING_DATABASE_PORT:-0}:6363"
130
+ - ${INFRAHUB_TESTING_DATABASE_BOLT_PORT:-0}:7687
129
131
 
130
132
  database-core3:
131
133
  deploy:
@@ -160,16 +162,37 @@ services:
160
162
  com.github.job: "${JOB_NAME:-unknown}"
161
163
  ports:
162
164
  - "${INFRAHUB_TESTING_DATABASE_PORT:-0}:6364"
165
+ - ${INFRAHUB_TESTING_DATABASE_BOLT_PORT:-0}:7687
163
166
 
164
167
  task-manager:
165
168
  image: "${INFRAHUB_TESTING_DOCKER_IMAGE}:${INFRAHUB_TESTING_IMAGE_VERSION}"
166
- command: uvicorn --host 0.0.0.0 --port 4200 --factory infrahub.prefect_server.app:create_infrahub_prefect
169
+ command: "${PREFECT_SERVER_COMMAND:-gunicorn -k uvicorn.workers.UvicornWorker -b 0.0.0.0:4200 'infrahub.prefect_server.app:create_infrahub_prefect()'}"
167
170
  depends_on:
168
171
  task-manager-db:
169
172
  condition: service_healthy
173
+ cache:
174
+ condition: service_healthy
170
175
  environment:
171
176
  PREFECT_UI_ENABLED: "${INFRAHUB_TESTING_PREFECT_UI_ENABLED}" # enabling UI requires permissions, run container as root to enable UI
172
177
  PREFECT_API_DATABASE_CONNECTION_URL: postgresql+asyncpg://postgres:postgres@task-manager-db:5432/prefect
178
+
179
+ INFRAHUB_CACHE_ADDRESS: ${INFRAHUB_TESTING_CACHE_ADDRESS}
180
+ PREFECT_REDIS_MESSAGING_HOST: "${INFRAHUB_TESTING_CACHE_ADDRESS:-cache}"
181
+ PREFECT_REDIS_MESSAGING_DB: "1"
182
+ PREFECT_REDIS_MESSAGING_CONSUMER_MIN_IDLE_TIME: "30"
183
+ PREFECT_REDIS_MESSAGING_PUBLISHER_BATCH_SIZE: "1"
184
+
185
+ PREFECT_MESSAGING_BROKER:
186
+ PREFECT_MESSAGING_CACHE:
187
+ PREFECT__SERVER_WEBSERVER_ONLY:
188
+ PREFECT_API_DATABASE_MIGRATE_ON_START:
189
+ PREFECT_API_BLOCKS_REGISTER_ON_START:
190
+ PREFECT_SERVER_SERVICES_EVENT_LOGGER_ENABLED:
191
+ PREFECT_SERVER_SERVICES_EVENT_PERSISTER_ENABLED:
192
+ PREFECT_SERVER_SERVICES_TRIGGERS_ENABLED:
193
+ PREFECT_SERVER_SERVICES_TASK_RUN_RECORDER_ENABLED:
194
+
195
+ WEB_CONCURRENCY: ${INFRAHUB_TESTING_TASKMGR_API_WORKERS:-1}
173
196
  healthcheck:
174
197
  test: /usr/local/bin/httpx http://localhost:4200/api/health || exit 1
175
198
  interval: 5s
@@ -179,8 +202,42 @@ services:
179
202
  ports:
180
203
  - ${INFRAHUB_TESTING_TASK_MANAGER_PORT:-0}:4200
181
204
 
205
+ task-manager-background-svc:
206
+ deploy:
207
+ mode: replicated
208
+ replicas: ${INFRAHUB_TESTING_TASKMGR_BACKGROUND_SVC_REPLICAS:-0}
209
+ image: "${INFRAHUB_TESTING_DOCKER_IMAGE}:${INFRAHUB_TESTING_IMAGE_VERSION}"
210
+ command: prefect server services start
211
+ restart: unless-stopped
212
+ depends_on:
213
+ task-manager:
214
+ condition: service_healthy
215
+ task-manager-db:
216
+ condition: service_healthy
217
+ cache:
218
+ condition: service_healthy
219
+ environment:
220
+ PREFECT_UI_ENABLED: "${INFRAHUB_TESTING_PREFECT_UI_ENABLED}" # This might be required because triggers actions service depends on an in-memory API server
221
+ PREFECT_API_DATABASE_CONNECTION_URL: postgresql+asyncpg://postgres:postgres@task-manager-db:5432/prefect
222
+
223
+ PREFECT_API_DATABASE_MIGRATE_ON_START: "false"
224
+ PREFECT_API_BLOCKS_REGISTER_ON_START: "false"
225
+ INFRAHUB_CACHE_ADDRESS: ${INFRAHUB_TESTING_CACHE_ADDRESS}
226
+ PREFECT_MESSAGING_BROKER: prefect_redis.messaging
227
+ PREFECT_MESSAGING_CACHE: prefect_redis.messaging
228
+ PREFECT_REDIS_MESSAGING_HOST: "${INFRAHUB_TESTING_CACHE_ADDRESS:-cache}"
229
+ PREFECT_REDIS_MESSAGING_DB: "1"
230
+ PREFECT_REDIS_MESSAGING_CONSUMER_MIN_IDLE_TIME: "30"
231
+ PREFECT_REDIS_MESSAGING_PUBLISHER_BATCH_SIZE: "1"
232
+
233
+ PREFECT_SERVER_SERVICES_EVENT_LOGGER_ENABLED: "true"
234
+ PREFECT_SERVER_SERVICES_EVENT_PERSISTER_ENABLED: "true"
235
+ PREFECT_SERVER_SERVICES_TRIGGERS_ENABLED: "true"
236
+ PREFECT_SERVER_SERVICES_TASK_RUN_RECORDER_ENABLED: "true"
237
+
182
238
  task-manager-db:
183
239
  image: "${POSTGRES_DOCKER_IMAGE:-postgres:16-alpine}"
240
+ command: postgres -c 'max_connections=${INFRAHUB_TESTING_TASK_MANAGER_DB_MAX_CONNECTIONS:-100}'
184
241
  environment:
185
242
  - POSTGRES_USER=postgres
186
243
  - POSTGRES_PASSWORD=postgres
@@ -262,6 +319,7 @@ services:
262
319
  INFRAHUB_DB_PROTOCOL: ${INFRAHUB_TESTING_DB_PROTOCOL:-neo4j}
263
320
  INFRAHUB_WORKFLOW_ADDRESS: ${INFRAHUB_TESTING_WORKFLOW_ADDRESS}
264
321
  INFRAHUB_TIMEOUT: ${INFRAHUB_TESTING_TIMEOUT}
322
+ INFRAHUB_PAGINATION_SIZE: ${INFRAHUB_TESTING_PAGINATION_SIZE:-50}
265
323
  PREFECT_API_URL: ${INFRAHUB_TESTING_PREFECT_API}
266
324
  # Tracing
267
325
  INFRAHUB_TRACE_ENABLE: ${INFRAHUB_TRACE_ENABLE:-false}
@@ -20,7 +20,7 @@ services:
20
20
  - ${INFRAHUB_TESTING_MESSAGE_QUEUE_PORT:-0}:15692
21
21
 
22
22
  cache:
23
- image: ${CACHE_DOCKER_IMAGE:-redis:7.2.4}
23
+ image: ${CACHE_DOCKER_IMAGE:-redis:7.2.11}
24
24
  restart: unless-stopped
25
25
  healthcheck:
26
26
  test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
@@ -72,17 +72,38 @@ services:
72
72
  start_period: 3s
73
73
  ports:
74
74
  - ${INFRAHUB_TESTING_DATABASE_PORT:-0}:6362
75
+ - ${INFRAHUB_TESTING_DATABASE_BOLT_PORT:-0}:7687
75
76
  - ${INFRAHUB_TESTING_DATABASE_UI_PORT:-0}:7474
76
77
 
77
78
  task-manager:
78
79
  image: "${INFRAHUB_TESTING_DOCKER_IMAGE}:${INFRAHUB_TESTING_IMAGE_VERSION}"
79
- command: uvicorn --host 0.0.0.0 --port 4200 --factory infrahub.prefect_server.app:create_infrahub_prefect
80
+ command: "${PREFECT_SERVER_COMMAND:-gunicorn -k uvicorn.workers.UvicornWorker -b 0.0.0.0:4200 'infrahub.prefect_server.app:create_infrahub_prefect()'}"
80
81
  depends_on:
81
82
  task-manager-db:
82
83
  condition: service_healthy
84
+ cache:
85
+ condition: service_healthy
83
86
  environment:
84
87
  PREFECT_UI_ENABLED: "${INFRAHUB_TESTING_PREFECT_UI_ENABLED}" # enabling UI requires permissions, run container as root to enable UI
85
88
  PREFECT_API_DATABASE_CONNECTION_URL: postgresql+asyncpg://postgres:postgres@task-manager-db:5432/prefect
89
+
90
+ INFRAHUB_CACHE_ADDRESS: ${INFRAHUB_TESTING_CACHE_ADDRESS}
91
+ PREFECT_REDIS_MESSAGING_HOST: "${INFRAHUB_TESTING_CACHE_ADDRESS:-cache}"
92
+ PREFECT_REDIS_MESSAGING_DB: "1"
93
+ PREFECT_REDIS_MESSAGING_CONSUMER_MIN_IDLE_TIME: "30"
94
+ PREFECT_REDIS_MESSAGING_PUBLISHER_BATCH_SIZE: "1"
95
+
96
+ PREFECT_MESSAGING_BROKER:
97
+ PREFECT_MESSAGING_CACHE:
98
+ PREFECT__SERVER_WEBSERVER_ONLY:
99
+ PREFECT_API_DATABASE_MIGRATE_ON_START:
100
+ PREFECT_API_BLOCKS_REGISTER_ON_START:
101
+ PREFECT_SERVER_SERVICES_EVENT_LOGGER_ENABLED:
102
+ PREFECT_SERVER_SERVICES_EVENT_PERSISTER_ENABLED:
103
+ PREFECT_SERVER_SERVICES_TRIGGERS_ENABLED:
104
+ PREFECT_SERVER_SERVICES_TASK_RUN_RECORDER_ENABLED:
105
+
106
+ WEB_CONCURRENCY: ${INFRAHUB_TESTING_TASKMGR_API_WORKERS:-1}
86
107
  healthcheck:
87
108
  test: /usr/local/bin/httpx http://localhost:4200/api/health || exit 1
88
109
  interval: 5s
@@ -92,8 +113,42 @@ services:
92
113
  ports:
93
114
  - ${INFRAHUB_TESTING_TASK_MANAGER_PORT:-0}:4200
94
115
 
116
+ task-manager-background-svc:
117
+ deploy:
118
+ mode: replicated
119
+ replicas: ${INFRAHUB_TESTING_TASKMGR_BACKGROUND_SVC_REPLICAS:-0}
120
+ image: "${INFRAHUB_TESTING_DOCKER_IMAGE}:${INFRAHUB_TESTING_IMAGE_VERSION}"
121
+ command: prefect server services start
122
+ restart: unless-stopped
123
+ depends_on:
124
+ task-manager:
125
+ condition: service_healthy
126
+ task-manager-db:
127
+ condition: service_healthy
128
+ cache:
129
+ condition: service_healthy
130
+ environment:
131
+ PREFECT_UI_ENABLED: "${INFRAHUB_TESTING_PREFECT_UI_ENABLED}" # This might be required because triggers actions service depends on an in-memory API server
132
+ PREFECT_API_DATABASE_CONNECTION_URL: postgresql+asyncpg://postgres:postgres@task-manager-db:5432/prefect
133
+
134
+ PREFECT_API_DATABASE_MIGRATE_ON_START: "false"
135
+ PREFECT_API_BLOCKS_REGISTER_ON_START: "false"
136
+ INFRAHUB_CACHE_ADDRESS: ${INFRAHUB_TESTING_CACHE_ADDRESS}
137
+ PREFECT_MESSAGING_BROKER: prefect_redis.messaging
138
+ PREFECT_MESSAGING_CACHE: prefect_redis.messaging
139
+ PREFECT_REDIS_MESSAGING_HOST: "${INFRAHUB_TESTING_CACHE_ADDRESS:-cache}"
140
+ PREFECT_REDIS_MESSAGING_DB: "1"
141
+ PREFECT_REDIS_MESSAGING_CONSUMER_MIN_IDLE_TIME: "30"
142
+ PREFECT_REDIS_MESSAGING_PUBLISHER_BATCH_SIZE: "1"
143
+
144
+ PREFECT_SERVER_SERVICES_EVENT_LOGGER_ENABLED: "true"
145
+ PREFECT_SERVER_SERVICES_EVENT_PERSISTER_ENABLED: "true"
146
+ PREFECT_SERVER_SERVICES_TRIGGERS_ENABLED: "true"
147
+ PREFECT_SERVER_SERVICES_TASK_RUN_RECORDER_ENABLED: "true"
148
+
95
149
  task-manager-db:
96
150
  image: "${POSTGRES_DOCKER_IMAGE:-postgres:16-alpine}"
151
+ command: postgres -c 'max_connections=${INFRAHUB_TESTING_TASK_MANAGER_DB_MAX_CONNECTIONS:-100}'
97
152
  environment:
98
153
  - POSTGRES_USER=postgres
99
154
  - POSTGRES_PASSWORD=postgres
@@ -171,6 +226,7 @@ services:
171
226
  INFRAHUB_DB_ADDRESS: ${INFRAHUB_TESTING_DB_ADDRESS:-database}
172
227
  INFRAHUB_WORKFLOW_ADDRESS: ${INFRAHUB_TESTING_WORKFLOW_ADDRESS}
173
228
  INFRAHUB_TIMEOUT: ${INFRAHUB_TESTING_TIMEOUT}
229
+ INFRAHUB_PAGINATION_SIZE: ${INFRAHUB_TESTING_PAGINATION_SIZE:-50}
174
230
  PREFECT_API_URL: ${INFRAHUB_TESTING_PREFECT_API}
175
231
  INFRAHUB_EXPERIMENTAL_VALUE_DB_INDEX:
176
232
  # Tracing
@@ -29,11 +29,14 @@ class TestInfrahubDocker:
29
29
  return result.stdout
30
30
 
31
31
  @staticmethod
32
- def execute_command(command: str, address: str, concurrent_execution: int = 10) -> subprocess.CompletedProcess[str]:
32
+ def execute_command(
33
+ command: str, address: str, concurrent_execution: int = 10, pagination_size: int = 50
34
+ ) -> subprocess.CompletedProcess[str]:
33
35
  env = os.environ.copy()
34
36
  env["INFRAHUB_ADDRESS"] = address
35
37
  env["INFRAHUB_API_TOKEN"] = PROJECT_ENV_VARIABLES["INFRAHUB_TESTING_INITIAL_ADMIN_TOKEN"]
36
38
  env["INFRAHUB_MAX_CONCURRENT_EXECUTION"] = f"{concurrent_execution}"
39
+ env["INFRAHUB_PAGINATION_SIZE"] = f"{pagination_size}"
37
40
  result = subprocess.run( # noqa: S602
38
41
  command, shell=True, capture_output=True, text=True, env=env, check=False
39
42
  )
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "infrahub-testcontainers"
3
- version = "1.3.0b3"
3
+ version = "1.4.13"
4
4
  requires-python = ">=3.9"
5
5
 
6
6
  [tool.poetry]
7
7
  name = "infrahub-testcontainers"
8
- version = "1.3.0b2"
8
+ version = "1.4.13"
9
9
  description = "Testcontainers instance for Infrahub to easily build integration tests"
10
10
  authors = ["OpsMill <info@opsmill.com>"]
11
11
  readme = "README.md"
@@ -33,12 +33,13 @@ infrahub_testcontainers = ["docker-compose.test.yml", "docker-compose-cluster.te
33
33
  "pytest-infrahub-performance-test" = "infrahub_testcontainers.plugin"
34
34
 
35
35
  [tool.poetry.dependencies]
36
- python = "^3.9"
36
+ python = "^3.9, < 3.14"
37
37
  testcontainers = "~4.8"
38
38
  psutil = "*"
39
39
  pytest = "*"
40
40
  httpx = "^0.28.1"
41
41
  pydantic = "^2.10.6"
42
+ prefect-client = "3.4.13"
42
43
 
43
44
  [tool.poetry.group.dev.dependencies]
44
45
  rich = "^13.9.4"