iris-devtester 1.8.1__py3-none-any.whl → 1.9.1__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.
- iris_devtester/__init__.py +3 -2
- iris_devtester/cli/__init__.py +4 -2
- iris_devtester/cli/__main__.py +1 -1
- iris_devtester/cli/connection_commands.py +31 -51
- iris_devtester/cli/container.py +42 -113
- iris_devtester/cli/container_commands.py +6 -4
- iris_devtester/cli/fixture_commands.py +97 -73
- iris_devtester/config/auto_discovery.py +8 -20
- iris_devtester/config/container_config.py +24 -35
- iris_devtester/config/container_state.py +19 -43
- iris_devtester/config/discovery.py +10 -10
- iris_devtester/config/presets.py +3 -10
- iris_devtester/config/yaml_loader.py +3 -2
- iris_devtester/connections/__init__.py +25 -30
- iris_devtester/connections/connection.py +4 -3
- iris_devtester/connections/dbapi.py +5 -1
- iris_devtester/connections/jdbc.py +2 -6
- iris_devtester/connections/manager.py +1 -1
- iris_devtester/connections/retry.py +2 -5
- iris_devtester/containers/__init__.py +6 -6
- iris_devtester/containers/cpf_manager.py +13 -12
- iris_devtester/containers/iris_container.py +268 -436
- iris_devtester/containers/models.py +18 -43
- iris_devtester/containers/monitor_utils.py +1 -3
- iris_devtester/containers/monitoring.py +31 -46
- iris_devtester/containers/performance.py +5 -5
- iris_devtester/containers/validation.py +27 -60
- iris_devtester/containers/wait_strategies.py +13 -4
- iris_devtester/fixtures/__init__.py +14 -13
- iris_devtester/fixtures/creator.py +127 -555
- iris_devtester/fixtures/loader.py +221 -78
- iris_devtester/fixtures/manifest.py +8 -6
- iris_devtester/fixtures/obj_export.py +45 -35
- iris_devtester/fixtures/validator.py +4 -7
- iris_devtester/integrations/langchain.py +2 -6
- iris_devtester/ports/registry.py +5 -4
- iris_devtester/testing/__init__.py +3 -0
- iris_devtester/testing/fixtures.py +10 -1
- iris_devtester/testing/helpers.py +5 -12
- iris_devtester/testing/models.py +3 -2
- iris_devtester/testing/schema_reset.py +1 -3
- iris_devtester/utils/__init__.py +20 -5
- iris_devtester/utils/container_port.py +2 -6
- iris_devtester/utils/container_status.py +2 -6
- iris_devtester/utils/dbapi_compat.py +29 -14
- iris_devtester/utils/enable_callin.py +5 -7
- iris_devtester/utils/health_checks.py +18 -33
- iris_devtester/utils/iris_container_adapter.py +27 -26
- iris_devtester/utils/password.py +673 -0
- iris_devtester/utils/progress.py +1 -1
- iris_devtester/utils/test_connection.py +4 -6
- {iris_devtester-1.8.1.dist-info → iris_devtester-1.9.1.dist-info}/METADATA +7 -7
- iris_devtester-1.9.1.dist-info/RECORD +66 -0
- {iris_devtester-1.8.1.dist-info → iris_devtester-1.9.1.dist-info}/WHEEL +1 -1
- iris_devtester/utils/password_reset.py +0 -594
- iris_devtester/utils/password_verification.py +0 -350
- iris_devtester/utils/unexpire_passwords.py +0 -168
- iris_devtester-1.8.1.dist-info/RECORD +0 -68
- {iris_devtester-1.8.1.dist-info → iris_devtester-1.9.1.dist-info}/entry_points.txt +0 -0
- {iris_devtester-1.8.1.dist-info → iris_devtester-1.9.1.dist-info}/licenses/LICENSE +0 -0
- {iris_devtester-1.8.1.dist-info → iris_devtester-1.9.1.dist-info}/top_level.txt +0 -0
|
@@ -9,24 +9,24 @@ Provides 5 commands:
|
|
|
9
9
|
- fixture info: Show detailed fixture information
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
import click
|
|
13
12
|
import sys
|
|
14
13
|
from pathlib import Path
|
|
15
14
|
from typing import Optional
|
|
16
15
|
|
|
16
|
+
import click
|
|
17
|
+
|
|
18
|
+
# Import IRISContainer for container handling
|
|
19
|
+
from iris_devtester.containers import IRISContainer
|
|
17
20
|
from iris_devtester.fixtures import (
|
|
18
|
-
FixtureCreator,
|
|
19
21
|
DATFixtureLoader,
|
|
20
|
-
FixtureValidator,
|
|
21
|
-
FixtureManifest,
|
|
22
22
|
FixtureCreateError,
|
|
23
|
+
FixtureCreator,
|
|
23
24
|
FixtureLoadError,
|
|
25
|
+
FixtureManifest,
|
|
24
26
|
FixtureValidationError,
|
|
27
|
+
FixtureValidator,
|
|
25
28
|
)
|
|
26
29
|
|
|
27
|
-
# Import IRISContainer for container handling
|
|
28
|
-
from iris_devtester.containers import IRISContainer
|
|
29
|
-
|
|
30
30
|
|
|
31
31
|
@click.group()
|
|
32
32
|
def fixture():
|
|
@@ -35,14 +35,24 @@ def fixture():
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
@fixture.command()
|
|
38
|
-
@click.option(
|
|
39
|
-
@click.option(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@click.option(
|
|
43
|
-
@click.option(
|
|
44
|
-
@click.option(
|
|
45
|
-
|
|
38
|
+
@click.option("--name", required=True, help='Fixture identifier (e.g., "test-entities-100")')
|
|
39
|
+
@click.option(
|
|
40
|
+
"--namespace", required=True, help='Source namespace to export (e.g., "USER_TEST_100")'
|
|
41
|
+
)
|
|
42
|
+
@click.option("--output", required=True, help="Output directory for fixture")
|
|
43
|
+
@click.option("--description", default="", help="Human-readable description")
|
|
44
|
+
@click.option("--version", default="1.0.0", help="Semantic version")
|
|
45
|
+
@click.option("--container", default=None, help="IRIS container name to use for fixture creation")
|
|
46
|
+
@click.option("--verbose", is_flag=True, help="Show detailed progress")
|
|
47
|
+
def create(
|
|
48
|
+
name: str,
|
|
49
|
+
namespace: str,
|
|
50
|
+
output: str,
|
|
51
|
+
description: str,
|
|
52
|
+
version: str,
|
|
53
|
+
container: str,
|
|
54
|
+
verbose: bool,
|
|
55
|
+
):
|
|
46
56
|
"""Create .DAT fixture by exporting IRIS namespace."""
|
|
47
57
|
try:
|
|
48
58
|
if verbose:
|
|
@@ -54,7 +64,9 @@ def create(name: str, namespace: str, output: str, description: str, version: st
|
|
|
54
64
|
# Attach to an existing container by name
|
|
55
65
|
container_obj = IRISContainer.attach(container)
|
|
56
66
|
except Exception as e:
|
|
57
|
-
click.secho(
|
|
67
|
+
click.secho(
|
|
68
|
+
f"\n❌ Failed to attach to container '{container}': {e}", fg="red", bold=True
|
|
69
|
+
)
|
|
58
70
|
sys.exit(1)
|
|
59
71
|
else:
|
|
60
72
|
# No container specified; start a temporary community container
|
|
@@ -63,7 +75,9 @@ def create(name: str, namespace: str, output: str, description: str, version: st
|
|
|
63
75
|
# Ensure it's started
|
|
64
76
|
container_obj.start()
|
|
65
77
|
except Exception as e:
|
|
66
|
-
click.secho(
|
|
78
|
+
click.secho(
|
|
79
|
+
f"\n❌ Failed to start community IRIS container: {e}", fg="red", bold=True
|
|
80
|
+
)
|
|
67
81
|
sys.exit(1)
|
|
68
82
|
creator = FixtureCreator(container=container_obj)
|
|
69
83
|
|
|
@@ -75,7 +89,7 @@ def create(name: str, namespace: str, output: str, description: str, version: st
|
|
|
75
89
|
namespace=namespace,
|
|
76
90
|
output_dir=output,
|
|
77
91
|
description=description,
|
|
78
|
-
version=version
|
|
92
|
+
version=version,
|
|
79
93
|
)
|
|
80
94
|
|
|
81
95
|
if verbose:
|
|
@@ -89,7 +103,7 @@ def create(name: str, namespace: str, output: str, description: str, version: st
|
|
|
89
103
|
table_count = len(manifest.tables)
|
|
90
104
|
total_rows = sum(t.row_count for t in manifest.tables)
|
|
91
105
|
|
|
92
|
-
click.secho(f"\n✅ Fixture created: {manifest.fixture_id}", fg=
|
|
106
|
+
click.secho(f"\n✅ Fixture created: {manifest.fixture_id}", fg="green", bold=True)
|
|
93
107
|
click.echo(f"\nLocation: {output}")
|
|
94
108
|
click.echo(f"Tables: {table_count}")
|
|
95
109
|
click.echo(f"Total rows: {total_rows:,}")
|
|
@@ -103,35 +117,38 @@ def create(name: str, namespace: str, output: str, description: str, version: st
|
|
|
103
117
|
sys.exit(0)
|
|
104
118
|
|
|
105
119
|
except FileExistsError as e:
|
|
106
|
-
click.secho(f"\n❌ Failed to create fixture", fg=
|
|
120
|
+
click.secho(f"\n❌ Failed to create fixture", fg="red", bold=True)
|
|
107
121
|
click.echo(f"\n{str(e)}")
|
|
108
122
|
sys.exit(1)
|
|
109
123
|
|
|
110
124
|
except FixtureCreateError as e:
|
|
111
|
-
click.secho(f"\n❌ Failed to create fixture", fg=
|
|
125
|
+
click.secho(f"\n❌ Failed to create fixture", fg="red", bold=True)
|
|
112
126
|
click.echo(f"\n{str(e)}")
|
|
113
127
|
sys.exit(3)
|
|
114
128
|
|
|
115
129
|
except ConnectionError as e:
|
|
116
|
-
click.secho(f"\n❌ Failed to create fixture", fg=
|
|
130
|
+
click.secho(f"\n❌ Failed to create fixture", fg="red", bold=True)
|
|
117
131
|
click.echo(f"\nConnection error: {str(e)}")
|
|
118
132
|
sys.exit(4)
|
|
119
133
|
|
|
120
134
|
except Exception as e:
|
|
121
|
-
click.secho(f"\n❌ Failed to create fixture", fg=
|
|
135
|
+
click.secho(f"\n❌ Failed to create fixture", fg="red", bold=True)
|
|
122
136
|
click.echo(f"\nUnexpected error: {str(e)}")
|
|
123
137
|
if verbose:
|
|
124
138
|
import traceback
|
|
139
|
+
|
|
125
140
|
traceback.print_exc()
|
|
126
141
|
sys.exit(1)
|
|
127
142
|
|
|
128
143
|
|
|
129
144
|
@fixture.command()
|
|
130
|
-
@click.option(
|
|
131
|
-
@click.option(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
@click.option(
|
|
145
|
+
@click.option("--fixture", required=True, help="Path to fixture directory")
|
|
146
|
+
@click.option(
|
|
147
|
+
"--namespace", default=None, help="Target namespace (default: use manifest namespace)"
|
|
148
|
+
)
|
|
149
|
+
@click.option("--no-validate", is_flag=True, help="Skip checksum validation (faster, less safe)")
|
|
150
|
+
@click.option("--force", is_flag=True, help="Force reload by deleting existing namespace")
|
|
151
|
+
@click.option("--verbose", is_flag=True, help="Show detailed progress")
|
|
135
152
|
def load(fixture: str, namespace: Optional[str], no_validate: bool, force: bool, verbose: bool):
|
|
136
153
|
"""Load .DAT fixture into IRIS database."""
|
|
137
154
|
try:
|
|
@@ -152,14 +169,16 @@ def load(fixture: str, namespace: Optional[str], no_validate: bool, force: bool,
|
|
|
152
169
|
fixture_path=fixture,
|
|
153
170
|
target_namespace=namespace,
|
|
154
171
|
validate_checksum=validate_checksum,
|
|
155
|
-
force_refresh=force
|
|
172
|
+
force_refresh=force,
|
|
156
173
|
)
|
|
157
174
|
|
|
158
175
|
# Calculate totals
|
|
159
176
|
table_count = len(result.tables_loaded)
|
|
160
|
-
total_rows = sum(
|
|
177
|
+
total_rows = sum(
|
|
178
|
+
t.row_count for t in result.manifest.tables if t.name in result.tables_loaded
|
|
179
|
+
)
|
|
161
180
|
|
|
162
|
-
click.secho(f"\n✅ Fixture loaded: {result.manifest.fixture_id}", fg=
|
|
181
|
+
click.secho(f"\n✅ Fixture loaded: {result.manifest.fixture_id}", fg="green", bold=True)
|
|
163
182
|
click.echo(f"\nNamespace: {result.namespace}")
|
|
164
183
|
click.echo(f"Tables loaded: {table_count}")
|
|
165
184
|
click.echo(f"Total rows: {total_rows:,}")
|
|
@@ -178,40 +197,41 @@ def load(fixture: str, namespace: Optional[str], no_validate: bool, force: bool,
|
|
|
178
197
|
sys.exit(0)
|
|
179
198
|
|
|
180
199
|
except FileNotFoundError as e:
|
|
181
|
-
click.secho(f"\n❌ Failed to load fixture", fg=
|
|
200
|
+
click.secho(f"\n❌ Failed to load fixture", fg="red", bold=True)
|
|
182
201
|
click.echo(f"\nFixture not found: {str(e)}")
|
|
183
202
|
sys.exit(1)
|
|
184
203
|
|
|
185
204
|
except FixtureValidationError as e:
|
|
186
|
-
click.secho(f"\n❌ Failed to load fixture", fg=
|
|
205
|
+
click.secho(f"\n❌ Failed to load fixture", fg="red", bold=True)
|
|
187
206
|
click.echo(f"\n{str(e)}")
|
|
188
207
|
sys.exit(2)
|
|
189
208
|
|
|
190
209
|
except FixtureLoadError as e:
|
|
191
|
-
click.secho(f"\n❌ Failed to load fixture", fg=
|
|
210
|
+
click.secho(f"\n❌ Failed to load fixture", fg="red", bold=True)
|
|
192
211
|
click.echo(f"\n{str(e)}")
|
|
193
212
|
click.echo("\nNote: Namespace mount is atomic (all-or-nothing operation)")
|
|
194
213
|
sys.exit(4)
|
|
195
214
|
|
|
196
215
|
except ConnectionError as e:
|
|
197
|
-
click.secho(f"\n❌ Failed to load fixture", fg=
|
|
216
|
+
click.secho(f"\n❌ Failed to load fixture", fg="red", bold=True)
|
|
198
217
|
click.echo(f"\nConnection error: {str(e)}")
|
|
199
218
|
sys.exit(5)
|
|
200
219
|
|
|
201
220
|
except Exception as e:
|
|
202
|
-
click.secho(f"\n❌ Failed to load fixture", fg=
|
|
221
|
+
click.secho(f"\n❌ Failed to load fixture", fg="red", bold=True)
|
|
203
222
|
click.echo(f"\nUnexpected error: {str(e)}")
|
|
204
223
|
if verbose:
|
|
205
224
|
import traceback
|
|
225
|
+
|
|
206
226
|
traceback.print_exc()
|
|
207
227
|
sys.exit(1)
|
|
208
228
|
|
|
209
229
|
|
|
210
230
|
@fixture.command()
|
|
211
|
-
@click.option(
|
|
212
|
-
@click.option(
|
|
213
|
-
@click.option(
|
|
214
|
-
@click.option(
|
|
231
|
+
@click.option("--fixture", required=True, help="Path to fixture directory")
|
|
232
|
+
@click.option("--no-checksums", is_flag=True, help="Skip checksum validation (faster)")
|
|
233
|
+
@click.option("--recalc", is_flag=True, help="Recalculate checksums and update manifest")
|
|
234
|
+
@click.option("--verbose", is_flag=True, help="Show detailed validation results")
|
|
215
235
|
def validate(fixture: str, no_checksums: bool, recalc: bool, verbose: bool):
|
|
216
236
|
"""Validate fixture integrity (manifest, files, checksums)."""
|
|
217
237
|
try:
|
|
@@ -222,7 +242,7 @@ def validate(fixture: str, no_checksums: bool, recalc: bool, verbose: bool):
|
|
|
222
242
|
click.echo("Recalculating checksums...")
|
|
223
243
|
|
|
224
244
|
manifest = validator.recalculate_checksums(fixture)
|
|
225
|
-
click.secho(f"\n✅ Checksums recalculated", fg=
|
|
245
|
+
click.secho(f"\n✅ Checksums recalculated", fg="green", bold=True)
|
|
226
246
|
click.echo(f"\nNew checksum: {manifest.checksum}")
|
|
227
247
|
sys.exit(0)
|
|
228
248
|
|
|
@@ -238,14 +258,14 @@ def validate(fixture: str, no_checksums: bool, recalc: bool, verbose: bool):
|
|
|
238
258
|
if result.valid:
|
|
239
259
|
manifest = result.manifest
|
|
240
260
|
if manifest is None:
|
|
241
|
-
click.secho("\n❌ Unexpected error: manifest is None", fg=
|
|
261
|
+
click.secho("\n❌ Unexpected error: manifest is None", fg="red", bold=True)
|
|
242
262
|
sys.exit(1)
|
|
243
263
|
sizes = validator.get_fixture_size(fixture)
|
|
244
264
|
|
|
245
265
|
table_count = len(manifest.tables)
|
|
246
266
|
total_rows = sum(t.row_count for t in manifest.tables)
|
|
247
267
|
|
|
248
|
-
click.secho(f"\n✅ Fixture is valid: {manifest.fixture_id}", fg=
|
|
268
|
+
click.secho(f"\n✅ Fixture is valid: {manifest.fixture_id}", fg="green", bold=True)
|
|
249
269
|
click.echo(f"\nFixture: {manifest.fixture_id}")
|
|
250
270
|
click.echo(f"Version: {manifest.version}")
|
|
251
271
|
click.echo(f"Schema: {manifest.schema_version}")
|
|
@@ -259,58 +279,61 @@ def validate(fixture: str, no_checksums: bool, recalc: bool, verbose: bool):
|
|
|
259
279
|
click.echo(f" - {table}")
|
|
260
280
|
|
|
261
281
|
if result.warnings:
|
|
262
|
-
click.secho(f"\nWarnings ({len(result.warnings)}):", fg=
|
|
282
|
+
click.secho(f"\nWarnings ({len(result.warnings)}):", fg="yellow")
|
|
263
283
|
for warning in result.warnings:
|
|
264
284
|
click.echo(f" - {warning}")
|
|
265
285
|
|
|
266
286
|
sys.exit(0)
|
|
267
287
|
|
|
268
288
|
else:
|
|
269
|
-
click.secho(f"\n❌ Fixture validation failed", fg=
|
|
289
|
+
click.secho(f"\n❌ Fixture validation failed", fg="red", bold=True)
|
|
270
290
|
click.echo(f"\nErrors ({len(result.errors)}):")
|
|
271
291
|
for error in result.errors:
|
|
272
292
|
click.echo(f" - {error}")
|
|
273
293
|
|
|
274
294
|
if result.warnings:
|
|
275
|
-
click.secho(f"\nWarnings ({len(result.warnings)}):", fg=
|
|
295
|
+
click.secho(f"\nWarnings ({len(result.warnings)}):", fg="yellow")
|
|
276
296
|
for warning in result.warnings:
|
|
277
297
|
click.echo(f" - {warning}")
|
|
278
298
|
|
|
279
299
|
sys.exit(1)
|
|
280
300
|
|
|
281
301
|
except FileNotFoundError as e:
|
|
282
|
-
click.secho(f"\n❌ Fixture not found", fg=
|
|
302
|
+
click.secho(f"\n❌ Fixture not found", fg="red", bold=True)
|
|
283
303
|
click.echo(f"\n{str(e)}")
|
|
284
304
|
sys.exit(2)
|
|
285
305
|
|
|
286
306
|
except Exception as e:
|
|
287
|
-
click.secho(f"\n❌ Validation failed", fg=
|
|
307
|
+
click.secho(f"\n❌ Validation failed", fg="red", bold=True)
|
|
288
308
|
click.echo(f"\nUnexpected error: {str(e)}")
|
|
289
309
|
if verbose:
|
|
290
310
|
import traceback
|
|
311
|
+
|
|
291
312
|
traceback.print_exc()
|
|
292
313
|
sys.exit(1)
|
|
293
314
|
|
|
294
315
|
|
|
295
316
|
@fixture.command()
|
|
296
|
-
@click.argument(
|
|
297
|
-
@click.option(
|
|
317
|
+
@click.argument("path", default="./fixtures")
|
|
318
|
+
@click.option("--verbose", is_flag=True, help="Show detailed fixture info")
|
|
298
319
|
def list(path: str, verbose: bool):
|
|
299
320
|
"""List available fixtures in directory."""
|
|
300
321
|
try:
|
|
301
322
|
fixtures_dir = Path(path)
|
|
302
323
|
|
|
303
324
|
if not fixtures_dir.exists():
|
|
304
|
-
click.secho(f"\n❌ Directory not found: {path}", fg=
|
|
325
|
+
click.secho(f"\n❌ Directory not found: {path}", fg="red")
|
|
305
326
|
sys.exit(1)
|
|
306
327
|
|
|
307
328
|
# Find all manifest.json files
|
|
308
329
|
manifests = list(fixtures_dir.rglob("manifest.json"))
|
|
309
330
|
|
|
310
331
|
if not manifests:
|
|
311
|
-
click.secho(f"\nNo fixtures found in {path}", fg=
|
|
332
|
+
click.secho(f"\nNo fixtures found in {path}", fg="yellow")
|
|
312
333
|
click.echo(f"\nTo create a fixture:")
|
|
313
|
-
click.echo(
|
|
334
|
+
click.echo(
|
|
335
|
+
f" iris-devtester fixture create --name my-fixture --namespace USER --output {path}/my-fixture"
|
|
336
|
+
)
|
|
314
337
|
sys.exit(0)
|
|
315
338
|
|
|
316
339
|
# Load and display fixtures
|
|
@@ -323,13 +346,11 @@ def list(path: str, verbose: bool):
|
|
|
323
346
|
manifest = FixtureManifest.from_file(str(manifest_file))
|
|
324
347
|
fixture_dir = manifest_file.parent
|
|
325
348
|
sizes = validator.get_fixture_size(str(fixture_dir))
|
|
326
|
-
total_size += sizes[
|
|
349
|
+
total_size += sizes["total_bytes"]
|
|
327
350
|
|
|
328
|
-
fixtures.append(
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
'size_mb': sizes['total_mb']
|
|
332
|
-
})
|
|
351
|
+
fixtures.append(
|
|
352
|
+
{"manifest": manifest, "path": fixture_dir, "size_mb": sizes["total_mb"]}
|
|
353
|
+
)
|
|
333
354
|
except Exception:
|
|
334
355
|
# Skip invalid fixtures
|
|
335
356
|
continue
|
|
@@ -337,13 +358,15 @@ def list(path: str, verbose: bool):
|
|
|
337
358
|
click.echo(f"\nAvailable fixtures in {path}:\n")
|
|
338
359
|
|
|
339
360
|
for f in fixtures:
|
|
340
|
-
manifest = f[
|
|
361
|
+
manifest = f["manifest"]
|
|
341
362
|
table_count = len(manifest.tables)
|
|
342
363
|
total_rows = sum(t.row_count for t in manifest.tables)
|
|
343
364
|
|
|
344
|
-
click.secho(f" {manifest.fixture_id}", fg=
|
|
365
|
+
click.secho(f" {manifest.fixture_id}", fg="cyan", bold=True)
|
|
345
366
|
click.echo(f" Version: {manifest.version}")
|
|
346
|
-
click.echo(
|
|
367
|
+
click.echo(
|
|
368
|
+
f" Tables: {table_count}, Rows: {total_rows:,}, Size: {f['size_mb']:.2f} MB"
|
|
369
|
+
)
|
|
347
370
|
if manifest.description:
|
|
348
371
|
click.echo(f" Description: {manifest.description}")
|
|
349
372
|
click.echo(f" Path: {f['path']}")
|
|
@@ -362,14 +385,14 @@ def list(path: str, verbose: bool):
|
|
|
362
385
|
sys.exit(0)
|
|
363
386
|
|
|
364
387
|
except Exception as e:
|
|
365
|
-
click.secho(f"\n❌ Failed to list fixtures", fg=
|
|
388
|
+
click.secho(f"\n❌ Failed to list fixtures", fg="red", bold=True)
|
|
366
389
|
click.echo(f"\nError: {str(e)}")
|
|
367
390
|
sys.exit(1)
|
|
368
391
|
|
|
369
392
|
|
|
370
393
|
@fixture.command()
|
|
371
|
-
@click.option(
|
|
372
|
-
@click.option(
|
|
394
|
+
@click.option("--fixture", required=True, help="Path to fixture directory")
|
|
395
|
+
@click.option("--json", "output_json", is_flag=True, help="Output as JSON")
|
|
373
396
|
def info(fixture: str, output_json: bool):
|
|
374
397
|
"""Show detailed information about fixture."""
|
|
375
398
|
try:
|
|
@@ -377,7 +400,7 @@ def info(fixture: str, output_json: bool):
|
|
|
377
400
|
manifest_file = Path(fixture) / "manifest.json"
|
|
378
401
|
|
|
379
402
|
if not manifest_file.exists():
|
|
380
|
-
click.secho(f"\n❌ Fixture not found: {fixture}", fg=
|
|
403
|
+
click.secho(f"\n❌ Fixture not found: {fixture}", fg="red")
|
|
381
404
|
sys.exit(1)
|
|
382
405
|
|
|
383
406
|
manifest = FixtureManifest.from_file(str(manifest_file))
|
|
@@ -385,6 +408,7 @@ def info(fixture: str, output_json: bool):
|
|
|
385
408
|
|
|
386
409
|
if output_json:
|
|
387
410
|
import json
|
|
411
|
+
|
|
388
412
|
data = {
|
|
389
413
|
"fixture_id": manifest.fixture_id,
|
|
390
414
|
"version": manifest.version,
|
|
@@ -395,13 +419,13 @@ def info(fixture: str, output_json: bool):
|
|
|
395
419
|
"namespace": manifest.namespace,
|
|
396
420
|
"tables": [{"name": t.name, "row_count": t.row_count} for t in manifest.tables],
|
|
397
421
|
"size": {
|
|
398
|
-
"total_mb": sizes[
|
|
399
|
-
"manifest_kb": sizes[
|
|
400
|
-
"dat_mb": sizes[
|
|
422
|
+
"total_mb": sizes["total_mb"],
|
|
423
|
+
"manifest_kb": sizes["manifest_kb"],
|
|
424
|
+
"dat_mb": sizes["dat_mb"],
|
|
401
425
|
},
|
|
402
426
|
"features": manifest.features,
|
|
403
427
|
"known_queries": manifest.known_queries,
|
|
404
|
-
"location": str(Path(fixture).resolve())
|
|
428
|
+
"location": str(Path(fixture).resolve()),
|
|
405
429
|
}
|
|
406
430
|
click.echo(json.dumps(data, indent=2))
|
|
407
431
|
else:
|
|
@@ -442,15 +466,15 @@ def info(fixture: str, output_json: bool):
|
|
|
442
466
|
sys.exit(0)
|
|
443
467
|
|
|
444
468
|
except FileNotFoundError as e:
|
|
445
|
-
click.secho(f"\n❌ Fixture not found", fg=
|
|
469
|
+
click.secho(f"\n❌ Fixture not found", fg="red")
|
|
446
470
|
click.echo(f"\n{str(e)}")
|
|
447
471
|
sys.exit(1)
|
|
448
472
|
|
|
449
473
|
except Exception as e:
|
|
450
|
-
click.secho(f"\n❌ Failed to show fixture info", fg=
|
|
474
|
+
click.secho(f"\n❌ Failed to show fixture info", fg="red", bold=True)
|
|
451
475
|
click.echo(f"\nError: {str(e)}")
|
|
452
476
|
sys.exit(1)
|
|
453
477
|
|
|
454
478
|
|
|
455
|
-
if __name__ ==
|
|
479
|
+
if __name__ == "__main__":
|
|
456
480
|
fixture()
|
|
@@ -13,12 +13,12 @@ import logging
|
|
|
13
13
|
import re
|
|
14
14
|
import subprocess
|
|
15
15
|
import sys
|
|
16
|
-
from typing import
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def discover_iris_port(test_ports:
|
|
21
|
+
def discover_iris_port(test_ports: Optional[List[int]] = None) -> Optional[int]:
|
|
22
22
|
"""
|
|
23
23
|
Auto-discover IRIS SuperServer port from running instances.
|
|
24
24
|
|
|
@@ -201,9 +201,7 @@ def discover_native_iris() -> Optional[Dict[str, Any]]:
|
|
|
201
201
|
"""
|
|
202
202
|
try:
|
|
203
203
|
# Run 'iris list' to see running instances
|
|
204
|
-
result = subprocess.run(
|
|
205
|
-
["iris", "list"], capture_output=True, text=True, timeout=5
|
|
206
|
-
)
|
|
204
|
+
result = subprocess.run(["iris", "list"], capture_output=True, text=True, timeout=5)
|
|
207
205
|
|
|
208
206
|
if result.returncode != 0:
|
|
209
207
|
logger.debug(f"'iris list' failed with exit code {result.returncode}")
|
|
@@ -224,9 +222,7 @@ def discover_native_iris() -> Optional[Dict[str, Any]]:
|
|
|
224
222
|
if match:
|
|
225
223
|
port = int(match.group(1))
|
|
226
224
|
|
|
227
|
-
logger.info(
|
|
228
|
-
f"✅ Discovered native IRIS on SuperServer port {port}"
|
|
229
|
-
)
|
|
225
|
+
logger.info(f"✅ Discovered native IRIS on SuperServer port {port}")
|
|
230
226
|
|
|
231
227
|
return {
|
|
232
228
|
"host": "localhost",
|
|
@@ -280,17 +276,13 @@ def auto_discover_iris() -> Optional[Dict[str, Any]]:
|
|
|
280
276
|
# Priority 1: Docker containers
|
|
281
277
|
config = discover_docker_iris()
|
|
282
278
|
if config:
|
|
283
|
-
logger.info(
|
|
284
|
-
f"✓ Auto-discovery successful (Docker): {config['host']}:{config['port']}"
|
|
285
|
-
)
|
|
279
|
+
logger.info(f"✓ Auto-discovery successful (Docker): {config['host']}:{config['port']}")
|
|
286
280
|
return config
|
|
287
281
|
|
|
288
282
|
# Priority 2: Native IRIS
|
|
289
283
|
config = discover_native_iris()
|
|
290
284
|
if config:
|
|
291
|
-
logger.info(
|
|
292
|
-
f"✓ Auto-discovery successful (Native): {config['host']}:{config['port']}"
|
|
293
|
-
)
|
|
285
|
+
logger.info(f"✓ Auto-discovery successful (Native): {config['host']}:{config['port']}")
|
|
294
286
|
return config
|
|
295
287
|
|
|
296
288
|
# Priority 3: Multi-port scanning
|
|
@@ -303,9 +295,7 @@ def auto_discover_iris() -> Optional[Dict[str, Any]]:
|
|
|
303
295
|
"password": "SYS",
|
|
304
296
|
"namespace": "USER",
|
|
305
297
|
}
|
|
306
|
-
logger.info(
|
|
307
|
-
f"✓ Auto-discovery successful (Port scan): {config['host']}:{config['port']}"
|
|
308
|
-
)
|
|
298
|
+
logger.info(f"✓ Auto-discovery successful (Port scan): {config['host']}:{config['port']}")
|
|
309
299
|
return config
|
|
310
300
|
|
|
311
301
|
logger.warning(
|
|
@@ -326,9 +316,7 @@ def auto_discover_iris() -> Optional[Dict[str, Any]]:
|
|
|
326
316
|
|
|
327
317
|
if __name__ == "__main__":
|
|
328
318
|
"""Quick test of auto-discovery."""
|
|
329
|
-
logging.basicConfig(
|
|
330
|
-
level=logging.INFO, format="%(levelname)s: %(message)s"
|
|
331
|
-
)
|
|
319
|
+
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
|
332
320
|
|
|
333
321
|
print("=" * 60)
|
|
334
322
|
print("IRIS Auto-Discovery Test")
|
|
@@ -52,55 +52,33 @@ class ContainerConfig(BaseModel):
|
|
|
52
52
|
"""
|
|
53
53
|
|
|
54
54
|
edition: Literal["community", "enterprise"] = Field(
|
|
55
|
-
default="community",
|
|
56
|
-
description="IRIS edition to use"
|
|
55
|
+
default="community", description="IRIS edition to use"
|
|
57
56
|
)
|
|
58
57
|
container_name: str = Field(
|
|
59
58
|
default="iris_db",
|
|
60
59
|
pattern=r"^[a-zA-Z0-9][a-zA-Z0-9_.-]*$",
|
|
61
|
-
description="Container name identifier"
|
|
60
|
+
description="Container name identifier",
|
|
62
61
|
)
|
|
63
62
|
superserver_port: int = Field(
|
|
64
|
-
default=1972,
|
|
65
|
-
ge=1024,
|
|
66
|
-
le=65535,
|
|
67
|
-
description="SuperServer port mapping"
|
|
63
|
+
default=1972, ge=1024, le=65535, description="SuperServer port mapping"
|
|
68
64
|
)
|
|
69
65
|
webserver_port: int = Field(
|
|
70
|
-
default=52773,
|
|
71
|
-
ge=1024,
|
|
72
|
-
le=65535,
|
|
73
|
-
description="Management Portal port mapping"
|
|
66
|
+
default=52773, ge=1024, le=65535, description="Management Portal port mapping"
|
|
74
67
|
)
|
|
75
68
|
namespace: str = Field(
|
|
76
|
-
default="USER",
|
|
77
|
-
pattern=r"^[A-Z][A-Z0-9%]*$",
|
|
78
|
-
description="Default IRIS namespace"
|
|
79
|
-
)
|
|
80
|
-
password: str = Field(
|
|
81
|
-
default="SYS",
|
|
82
|
-
min_length=1,
|
|
83
|
-
description="_SYSTEM user password"
|
|
69
|
+
default="USER", pattern=r"^[A-Z][A-Z0-9%]*$", description="Default IRIS namespace"
|
|
84
70
|
)
|
|
71
|
+
password: str = Field(default="SYS", min_length=1, description="_SYSTEM user password")
|
|
85
72
|
license_key: Optional[str] = Field(
|
|
86
|
-
default=None,
|
|
87
|
-
description="License key for Enterprise edition"
|
|
88
|
-
)
|
|
89
|
-
volumes: List[str] = Field(
|
|
90
|
-
default_factory=list,
|
|
91
|
-
description="Volume mount strings"
|
|
73
|
+
default=None, description="License key for Enterprise edition"
|
|
92
74
|
)
|
|
75
|
+
volumes: List[str] = Field(default_factory=list, description="Volume mount strings")
|
|
93
76
|
image: Optional[str] = Field(
|
|
94
|
-
default=None,
|
|
95
|
-
description="Full Docker image name (overrides edition/image_tag)"
|
|
96
|
-
)
|
|
97
|
-
image_tag: str = Field(
|
|
98
|
-
default="latest",
|
|
99
|
-
description="Docker image tag"
|
|
77
|
+
default=None, description="Full Docker image name (overrides edition/image_tag)"
|
|
100
78
|
)
|
|
79
|
+
image_tag: str = Field(default="latest", description="Docker image tag")
|
|
101
80
|
cpf_merge: Optional[str] = Field(
|
|
102
|
-
default=None,
|
|
103
|
-
description="Path to CPF merge file or raw CPF content"
|
|
81
|
+
default=None, description="Path to CPF merge file or raw CPF content"
|
|
104
82
|
)
|
|
105
83
|
|
|
106
84
|
@field_validator("container_name")
|
|
@@ -170,7 +148,17 @@ class ContainerConfig(BaseModel):
|
|
|
170
148
|
config_data = {}
|
|
171
149
|
|
|
172
150
|
# Map direct fields
|
|
173
|
-
for field in [
|
|
151
|
+
for field in [
|
|
152
|
+
"edition",
|
|
153
|
+
"container_name",
|
|
154
|
+
"namespace",
|
|
155
|
+
"password",
|
|
156
|
+
"license_key",
|
|
157
|
+
"volumes",
|
|
158
|
+
"image",
|
|
159
|
+
"image_tag",
|
|
160
|
+
"cpf_merge",
|
|
161
|
+
]:
|
|
174
162
|
if field in yaml_data:
|
|
175
163
|
config_data[field] = yaml_data[field]
|
|
176
164
|
|
|
@@ -304,6 +292,7 @@ class ContainerConfig(BaseModel):
|
|
|
304
292
|
|
|
305
293
|
class Config:
|
|
306
294
|
"""Pydantic model configuration."""
|
|
295
|
+
|
|
307
296
|
json_schema_extra = {
|
|
308
297
|
"example": {
|
|
309
298
|
"edition": "community",
|
|
@@ -314,6 +303,6 @@ class ContainerConfig(BaseModel):
|
|
|
314
303
|
"password": "SYS",
|
|
315
304
|
"license_key": None,
|
|
316
305
|
"volumes": ["./data:/external"],
|
|
317
|
-
"image_tag": "latest"
|
|
306
|
+
"image_tag": "latest",
|
|
318
307
|
}
|
|
319
308
|
}
|