dh-cli 0.8.6__tar.gz → 0.8.7__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.
- {dh_cli-0.8.6 → dh_cli-0.8.7}/PKG-INFO +1 -1
- {dh_cli-0.8.6 → dh_cli-0.8.7}/pyproject.toml +1 -1
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/bedrock/commands.py +14 -28
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/test_key_command.py +47 -20
- {dh_cli-0.8.6 → dh_cli-0.8.7}/.gitignore +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/LICENSE +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/README.md +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/__init__.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/_identity.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/__init__.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/aws_batch.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/__init__.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/boltz.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/cancel.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/clean.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/embed_t5.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/finalize.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/list_jobs.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/local.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/logs.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/orca.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/protmpnn.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/protmpnn_to_boltz.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/retry.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/status.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/submit.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/train.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/commands/wait_for.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/fasta_utils.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/h5_utils.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/job_id.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/manifest.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/batch/s3_transport.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/bedrock/__init__.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/bedrock/cost_report.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/bedrock/pricing.yaml +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/cloud_commands.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/codeartifact.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/engines_studios/__init__.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/engines_studios/api_client.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/engines_studios/auth.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/engines_studios/engine_commands.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/engines_studios/progress.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/engines_studios/ssh_config.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/engines_studios/studio_commands.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/github_commands.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/hz/__init__.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/hz/deploy.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/hz/local.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/hz/test.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/hz/tf.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/hz/users.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/main.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/utility_commands.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/src/dh_cli/warehouse.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/batch/__init__.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/batch/test_aws_batch_resources.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/batch/test_image_override.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/batch/test_submit_cpu_only.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/batch/test_submit_image_validation.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/batch/test_submit_merge.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/conftest.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/fixtures/A_cache_write.json +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/fixtures/B_cache_read.json +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/fixtures/C_plain.json +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/fixtures/D_cursor_user.json +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/fixtures/E_service_role.json +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/fixtures/F_legacy_shared.json +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/fixtures/G_unknown_model.json +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/test_build_report.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/test_classify_arn.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/test_cli_exit_codes.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/test_cost_calc.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/test_cost_command.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/test_cur_reconciliation.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/test_render_formats.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/test_resolve_base_model.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/bedrock/test_s3_walker.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/github/__init__.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/github/conftest.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/github/test_engine_role_cannot_read_github_pat.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/github/test_identity.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/github/test_login.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/github/test_login_error_paths.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/github/test_login_security.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/github/test_logout.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/github/test_rotate.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/github/test_status.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/hz/test_init.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/hz/test_suites.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/hz/test_users.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/test_cloud_gcp.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/test_finalize_boltz_tar.py +0 -0
- {dh_cli-0.8.6 → dh_cli-0.8.7}/tests/test_finalize_protmpnn.py +0 -0
|
@@ -22,6 +22,8 @@ from typing import Optional
|
|
|
22
22
|
|
|
23
23
|
import click
|
|
24
24
|
|
|
25
|
+
from dh_cli._identity import HandleResolutionError, resolve_handle_from_session
|
|
26
|
+
|
|
25
27
|
from . import cost_report as cr
|
|
26
28
|
|
|
27
29
|
# Hard-coded for the dev account. Kept here (not as user-facing flags)
|
|
@@ -58,37 +60,21 @@ def bedrock():
|
|
|
58
60
|
# --------------------------------------------------------------------------
|
|
59
61
|
|
|
60
62
|
|
|
61
|
-
def
|
|
62
|
-
"""
|
|
63
|
-
|
|
64
|
-
A DeveloperAccess SSO caller identity looks like::
|
|
63
|
+
def _resolve_handle() -> str:
|
|
64
|
+
"""Resolve the developer handle from the current SSO session.
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
Thin wrapper around `_identity.resolve_handle_from_session` that
|
|
67
|
+
builds a boto3 Session and turns `HandleResolutionError` into a
|
|
68
|
+
`ClickException` so Click prints it cleanly. Shared with `dh gh`
|
|
69
|
+
via `_identity.py` so both commands behave identically when the
|
|
70
|
+
caller's session can't be classified.
|
|
70
71
|
"""
|
|
71
72
|
import boto3
|
|
72
73
|
|
|
73
74
|
try:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
raise click.ClickException(
|
|
78
|
-
f"Could not call sts:GetCallerIdentity to resolve your handle: {exc}\n"
|
|
79
|
-
"Run `awslogin dev-devaccess` (or pass --handle explicitly)."
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
principal = cr.classify_arn(arn)
|
|
83
|
-
# Accept any AWSReservedSSO_* session: DeveloperAccess,
|
|
84
|
-
# DevAdminAccess, SecurityAdministrator, etc. all carry the
|
|
85
|
-
# IdC username as the role-session-name, which is what we want
|
|
86
|
-
# as the developer handle.
|
|
87
|
-
if principal.principal_type in ("claude-code", "claude-code-other", "cursor"):
|
|
88
|
-
return principal.principal_name
|
|
89
|
-
raise click.ClickException(
|
|
90
|
-
f"Couldn't infer a developer handle from your identity ({arn}). Pass --handle explicitly."
|
|
91
|
-
)
|
|
75
|
+
return resolve_handle_from_session(boto3.Session())
|
|
76
|
+
except HandleResolutionError as exc:
|
|
77
|
+
raise click.ClickException(str(exc))
|
|
92
78
|
|
|
93
79
|
|
|
94
80
|
# --------------------------------------------------------------------------
|
|
@@ -135,7 +121,7 @@ def bedrock_key(handle: Optional[str], region: str, mode: str):
|
|
|
135
121
|
"""
|
|
136
122
|
import boto3
|
|
137
123
|
|
|
138
|
-
resolved = handle or
|
|
124
|
+
resolved = handle or _resolve_handle()
|
|
139
125
|
secret_id = f"{_SECRET_PREFIX}{resolved}"
|
|
140
126
|
|
|
141
127
|
try:
|
|
@@ -316,7 +302,7 @@ def bedrock_cost(
|
|
|
316
302
|
|
|
317
303
|
my_handle: Optional[str] = None
|
|
318
304
|
if me:
|
|
319
|
-
my_handle =
|
|
305
|
+
my_handle = _resolve_handle()
|
|
320
306
|
|
|
321
307
|
try:
|
|
322
308
|
records = cr.walk_logs(
|
|
@@ -32,15 +32,32 @@ def _build_sts_stub(arn: str = _DEV_ARN) -> MagicMock:
|
|
|
32
32
|
return sts
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def _build_session_stub(sts: MagicMock) -> MagicMock:
|
|
36
|
+
"""boto3.Session() stub whose .client('sts') returns `sts`.
|
|
37
|
+
|
|
38
|
+
Handle resolution flows through `_identity.resolve_handle_from_session`,
|
|
39
|
+
which calls `session.client('sts')`. The Secrets Manager fetch in
|
|
40
|
+
`bedrock_key` still goes through `boto3.client('secretsmanager', ...)`
|
|
41
|
+
directly, which is patched separately.
|
|
42
|
+
"""
|
|
43
|
+
session = MagicMock()
|
|
44
|
+
session.client.return_value = sts
|
|
45
|
+
return session
|
|
46
|
+
|
|
47
|
+
|
|
35
48
|
def _patch_boto3(*, sts=None, sm=None):
|
|
49
|
+
sts_stub = sts or _build_sts_stub()
|
|
50
|
+
session_stub = _build_session_stub(sts_stub)
|
|
51
|
+
|
|
36
52
|
def _client(name, **_kwargs):
|
|
37
|
-
if name == "sts":
|
|
38
|
-
return sts or _build_sts_stub()
|
|
39
53
|
if name == "secretsmanager":
|
|
40
54
|
return sm or _build_sm_stub()
|
|
41
|
-
raise AssertionError(f"unexpected boto3
|
|
55
|
+
raise AssertionError(f"unexpected boto3.client call: {name}")
|
|
42
56
|
|
|
43
|
-
return
|
|
57
|
+
return (
|
|
58
|
+
patch("boto3.Session", return_value=session_stub),
|
|
59
|
+
patch("boto3.client", side_effect=_client),
|
|
60
|
+
)
|
|
44
61
|
|
|
45
62
|
|
|
46
63
|
def _invoke(args):
|
|
@@ -52,7 +69,8 @@ def _invoke(args):
|
|
|
52
69
|
|
|
53
70
|
def test_key_default_prints_json_and_resolves_handle_from_sts():
|
|
54
71
|
sm = _build_sm_stub()
|
|
55
|
-
|
|
72
|
+
p_sess, p_cli = _patch_boto3(sts=_build_sts_stub(), sm=sm)
|
|
73
|
+
with p_sess, p_cli:
|
|
56
74
|
result = _invoke([])
|
|
57
75
|
assert result.exit_code == 0, result.output
|
|
58
76
|
sm.get_secret_value.assert_called_once_with(SecretId="cursor-bedrock/dma")
|
|
@@ -66,26 +84,28 @@ def test_key_handle_override_skips_sts():
|
|
|
66
84
|
can still fetch (for e.g. break-glass debugging)."""
|
|
67
85
|
sm = _build_sm_stub()
|
|
68
86
|
|
|
69
|
-
called: dict[str, int] = {"
|
|
87
|
+
called: dict[str, int] = {"session": 0}
|
|
88
|
+
|
|
89
|
+
def _session_factory(*_args, **_kwargs):
|
|
90
|
+
called["session"] += 1
|
|
91
|
+
return _build_session_stub(_build_sts_stub("arn:aws:iam::074735440724:root"))
|
|
70
92
|
|
|
71
93
|
def _client(name, **_kwargs):
|
|
72
|
-
if name == "sts":
|
|
73
|
-
called["sts"] += 1
|
|
74
|
-
return _build_sts_stub("arn:aws:iam::074735440724:root")
|
|
75
94
|
if name == "secretsmanager":
|
|
76
95
|
return sm
|
|
77
96
|
raise AssertionError(name)
|
|
78
97
|
|
|
79
|
-
with patch("boto3.client", side_effect=_client):
|
|
98
|
+
with patch("boto3.Session", side_effect=_session_factory), patch("boto3.client", side_effect=_client):
|
|
80
99
|
result = _invoke(["--handle", "jason"])
|
|
81
100
|
assert result.exit_code == 0, result.output
|
|
82
|
-
assert called["
|
|
101
|
+
assert called["session"] == 0
|
|
83
102
|
sm.get_secret_value.assert_called_once_with(SecretId="cursor-bedrock/jason")
|
|
84
103
|
|
|
85
104
|
|
|
86
105
|
def test_key_export_mode_prints_shell_exports():
|
|
87
106
|
sm = _build_sm_stub()
|
|
88
|
-
|
|
107
|
+
p_sess, p_cli = _patch_boto3(sm=sm)
|
|
108
|
+
with p_sess, p_cli:
|
|
89
109
|
result = _invoke(["--export"])
|
|
90
110
|
assert result.exit_code == 0, result.output
|
|
91
111
|
out = result.output
|
|
@@ -96,7 +116,8 @@ def test_key_export_mode_prints_shell_exports():
|
|
|
96
116
|
|
|
97
117
|
def test_key_secret_fetch_failure_exits_non_zero():
|
|
98
118
|
sm = _build_sm_stub(RuntimeError("AccessDeniedException: secretsmanager:GetSecretValue"))
|
|
99
|
-
|
|
119
|
+
p_sess, p_cli = _patch_boto3(sm=sm)
|
|
120
|
+
with p_sess, p_cli:
|
|
100
121
|
result = _invoke(["--handle", "nobody"])
|
|
101
122
|
assert result.exit_code != 0
|
|
102
123
|
assert "Could not read secret" in result.output
|
|
@@ -104,24 +125,30 @@ def test_key_secret_fetch_failure_exits_non_zero():
|
|
|
104
125
|
|
|
105
126
|
def test_key_unrecognised_caller_arn_exits_with_hint():
|
|
106
127
|
"""If STS returns something we can't classify, the error message must
|
|
107
|
-
tell the user to pass --handle explicitly instead of silently guessing.
|
|
128
|
+
tell the user to pass --handle explicitly instead of silently guessing.
|
|
129
|
+
|
|
130
|
+
Message comes from `_identity.HandleResolutionError` (shared with
|
|
131
|
+
`dh gh login`), so both commands surface the same hint."""
|
|
108
132
|
sts = _build_sts_stub("arn:aws:iam::074735440724:federated-user/weird")
|
|
109
|
-
|
|
133
|
+
p_sess, p_cli = _patch_boto3(sts=sts)
|
|
134
|
+
with p_sess, p_cli:
|
|
110
135
|
result = _invoke([])
|
|
111
136
|
assert result.exit_code != 0
|
|
112
137
|
assert "--handle" in result.output
|
|
138
|
+
assert "awslogin" in result.output
|
|
113
139
|
|
|
114
140
|
|
|
115
141
|
def test_key_resolves_handle_from_dev_admin_access_session():
|
|
116
142
|
"""DevAdminAccess (and any other AWSReservedSSO_* role) must resolve
|
|
117
143
|
to the session-name as the developer handle. Regression: earlier the
|
|
118
144
|
gate only accepted DeveloperAccess sessions, which broke `dh bedrock
|
|
119
|
-
key` for anyone running under an admin profile.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
)
|
|
145
|
+
key` for anyone running under an admin profile. The shared
|
|
146
|
+
`_identity` helper uses a regex that matches any `AWSReservedSSO_*`
|
|
147
|
+
role by construction."""
|
|
148
|
+
sts = _build_sts_stub("arn:aws:sts::074735440724:assumed-role/AWSReservedSSO_DevAdminAccess_2506d659c73583cc/dma")
|
|
123
149
|
sm = _build_sm_stub()
|
|
124
|
-
|
|
150
|
+
p_sess, p_cli = _patch_boto3(sts=sts, sm=sm)
|
|
151
|
+
with p_sess, p_cli:
|
|
125
152
|
result = _invoke([])
|
|
126
153
|
assert result.exit_code == 0, result.output
|
|
127
154
|
sm.get_secret_value.assert_called_once_with(SecretId="cursor-bedrock/dma")
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|