github2gerrit 0.1.11__tar.gz → 0.1.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.
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/PKG-INFO +1 -1
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/action.yaml +9 -3
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/cli.py +1 -1
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/core.py +66 -12
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/duplicate_detection.py +5 -15
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/ssh_discovery.py +131 -9
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_action_step_validation.py +9 -2
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_duplicate_detection.py +7 -14
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/.editorconfig +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/.gitignore +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/.gitlint +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/.markdownlint.yaml +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/.pre-commit-config.yaml +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/.readthedocs.yml +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/.yamllint +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/LICENSE +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/LICENSES/Apache-2.0.txt +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/README.md +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/REUSE.toml +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/REVISION_PLAN.md +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/docs/COMPOSITE_ACTION_TESTING.md +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/docs/github2gerrit_token_permissions_classic.png +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/pyproject.toml +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/sitecustomize.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/__init__.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/commit_normalization.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/config.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/external_api.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/gerrit_query.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/gerrit_rest.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/gerrit_urls.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/github_api.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/gitutils.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/mapping_comment.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/models.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/orchestrator/__init__.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/orchestrator/reconciliation.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/pr_content_filter.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/reconcile_matcher.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/rich_display.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/rich_logging.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/similarity.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/ssh_agent_setup.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/ssh_common.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/trailers.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/utils.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/conftest.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/fixtures/__init__.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/fixtures/make_repo.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_action_environment_mapping.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_action_outputs.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_action_pr_number_handling.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_change_id_deduplication.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_cli.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_cli_helpers.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_cli_outputs_file.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_cli_url_and_dryrun.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_commit_normalization.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_composite_action_coverage.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_config_and_reviewers.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_config_helpers.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_core_close_pr_policy.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_core_config_and_errors.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_core_gerrit_backref_comment.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_core_gerrit_push_errors.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_core_gerrit_rest_results.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_core_integration_fixture_repo.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_core_prepare_commits.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_core_ssh_setup.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_email_case_normalization.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_external_api_framework.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_gerrit_change_id_footer.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_gerrit_rest_client.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_gerrit_urls.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_gerrit_urls_more.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_ghe_and_gitreview_args.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_github_api_helpers.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_github_api_retry_and_helpers.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_gitutils_helpers.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_mapping_comment_additional.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_mapping_comment_digest_and_backref.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_metadata_and_reconciliation.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_misc_small_coverage.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_orphan_rest_side_effects.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_pr_content_filter.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_pr_content_filter_integration.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_reconciliation_extracted_module.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_reconciliation_plan_and_orphans.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_reconciliation_scenarios.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_ssh_agent.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_ssh_common.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_ssh_discovery.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_trailers_additional.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_url_parser.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_utils.py +0 -0
- {github2gerrit-0.1.11 → github2gerrit-0.1.13}/uv.lock +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: github2gerrit
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.13
|
4
4
|
Summary: Submit a GitHub pull request to a Gerrit repository.
|
5
5
|
Project-URL: Homepage, https://github.com/lfreleng-actions/github2gerrit
|
6
6
|
Project-URL: Repository, https://github.com/lfreleng-actions/github2gerrit
|
@@ -132,7 +132,7 @@ runs:
|
|
132
132
|
# yamllint disable-line rule:line-length
|
133
133
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
134
134
|
with:
|
135
|
-
python-version-file: 'pyproject.toml'
|
135
|
+
python-version-file: '${{ github.action_path }}/pyproject.toml'
|
136
136
|
|
137
137
|
- name: Setup uv
|
138
138
|
# yamllint disable-line rule:line-length
|
@@ -186,8 +186,14 @@ runs:
|
|
186
186
|
run: |
|
187
187
|
set -euo pipefail
|
188
188
|
uv --version
|
189
|
-
# Install
|
190
|
-
|
189
|
+
# Install from PyPI when called from external repos, local source for self-testing
|
190
|
+
if [[ "${{ github.repository }}" == "modeseven-lfreleng-actions/github2gerrit-action" ]]; then
|
191
|
+
echo "Installing from local source (self-testing)"
|
192
|
+
uv pip install --system ${{ github.action_path }}
|
193
|
+
else
|
194
|
+
echo "Installing from PyPI (external repository)"
|
195
|
+
uv pip install --system github2gerrit
|
196
|
+
fi
|
191
197
|
|
192
198
|
- name: Validate PR_NUMBER usage (non-dispatch)
|
193
199
|
# yamllint disable-line rule:line-length
|
@@ -525,7 +525,7 @@ def main(
|
|
525
525
|
if _is_github_actions_context():
|
526
526
|
try:
|
527
527
|
app_version = get_version("github2gerrit")
|
528
|
-
log.
|
528
|
+
log.debug("github2gerrit version %s", app_version)
|
529
529
|
except Exception:
|
530
530
|
log.warning("Version information not available")
|
531
531
|
|
@@ -1115,6 +1115,41 @@ class Orchestrator:
|
|
1115
1115
|
log.debug("SSH private key not provided, skipping SSH setup")
|
1116
1116
|
return
|
1117
1117
|
|
1118
|
+
# Check for ssh-keyscan availability early if auto-discovery needed
|
1119
|
+
if (
|
1120
|
+
auto_discover_gerrit_host_keys is not None
|
1121
|
+
and not inputs.gerrit_known_hosts
|
1122
|
+
):
|
1123
|
+
import shutil
|
1124
|
+
|
1125
|
+
keyscan_path = shutil.which("ssh-keyscan")
|
1126
|
+
if not keyscan_path:
|
1127
|
+
log.error(
|
1128
|
+
"❌ ssh-keyscan not found in PATH but is required for SSH "
|
1129
|
+
"host key auto-discovery"
|
1130
|
+
)
|
1131
|
+
log.error(
|
1132
|
+
"Available tools in PATH: %s",
|
1133
|
+
", ".join(
|
1134
|
+
[
|
1135
|
+
tool
|
1136
|
+
for tool in [
|
1137
|
+
"ssh",
|
1138
|
+
"ssh-keygen",
|
1139
|
+
"ssh-add",
|
1140
|
+
"ssh-agent",
|
1141
|
+
"ssh-keyscan",
|
1142
|
+
]
|
1143
|
+
if shutil.which(tool)
|
1144
|
+
]
|
1145
|
+
),
|
1146
|
+
)
|
1147
|
+
log.error("To fix this issue:")
|
1148
|
+
log.error("1. Install openssh-client package, OR")
|
1149
|
+
log.error("2. Provide GERRIT_KNOWN_HOSTS manually")
|
1150
|
+
else:
|
1151
|
+
log.debug("✅ ssh-keyscan found at: %s", keyscan_path)
|
1152
|
+
|
1118
1153
|
# Auto-discover or augment host keys (merge missing
|
1119
1154
|
# types/[host]:port entries)
|
1120
1155
|
effective_known_hosts = inputs.gerrit_known_hosts
|
@@ -1122,9 +1157,18 @@ class Orchestrator:
|
|
1122
1157
|
try:
|
1123
1158
|
if not effective_known_hosts:
|
1124
1159
|
log.info(
|
1125
|
-
"GERRIT_KNOWN_HOSTS not provided, attempting "
|
1126
|
-
"auto-discovery..."
|
1160
|
+
"🔍 GERRIT_KNOWN_HOSTS not provided, attempting "
|
1161
|
+
"auto-discovery for %s:%d...",
|
1162
|
+
gerrit.host,
|
1163
|
+
gerrit.port,
|
1127
1164
|
)
|
1165
|
+
log.debug(
|
1166
|
+
"Auto-discovery params: host=%s, port=%d, org=%s",
|
1167
|
+
gerrit.host,
|
1168
|
+
gerrit.port,
|
1169
|
+
inputs.organization,
|
1170
|
+
)
|
1171
|
+
|
1128
1172
|
discovered_keys = auto_discover_gerrit_host_keys(
|
1129
1173
|
gerrit_hostname=gerrit.host,
|
1130
1174
|
gerrit_port=gerrit.port,
|
@@ -1134,15 +1178,18 @@ class Orchestrator:
|
|
1134
1178
|
if discovered_keys:
|
1135
1179
|
effective_known_hosts = discovered_keys
|
1136
1180
|
log.info(
|
1137
|
-
"Successfully auto-discovered SSH host keys for "
|
1181
|
+
"✅ Successfully auto-discovered SSH host keys for "
|
1138
1182
|
"%s:%d",
|
1139
1183
|
gerrit.host,
|
1140
1184
|
gerrit.port,
|
1141
1185
|
)
|
1142
1186
|
else:
|
1143
|
-
log.
|
1144
|
-
"Auto-discovery failed
|
1145
|
-
"
|
1187
|
+
log.error(
|
1188
|
+
"❌ Auto-discovery failed for %s:%d - SSH host key "
|
1189
|
+
"verification will likely fail. Check network "
|
1190
|
+
"connectivity and ssh-keyscan availability.",
|
1191
|
+
gerrit.host,
|
1192
|
+
gerrit.port,
|
1146
1193
|
)
|
1147
1194
|
else:
|
1148
1195
|
# Provided known_hosts exists; ensure it contains
|
@@ -1191,15 +1238,22 @@ class Orchestrator:
|
|
1191
1238
|
"Auto-discovery returned no keys; known_hosts "
|
1192
1239
|
"not augmented"
|
1193
1240
|
)
|
1194
|
-
except Exception
|
1195
|
-
log.
|
1196
|
-
"SSH host key auto-discovery/augmentation failed
|
1241
|
+
except Exception:
|
1242
|
+
log.exception(
|
1243
|
+
"❌ SSH host key auto-discovery/augmentation failed "
|
1244
|
+
"for %s:%d",
|
1245
|
+
gerrit.host,
|
1246
|
+
gerrit.port,
|
1197
1247
|
)
|
1198
1248
|
|
1199
1249
|
if not effective_known_hosts:
|
1200
|
-
log.
|
1201
|
-
"No SSH host keys available (manual or auto-discovered)
|
1202
|
-
"
|
1250
|
+
log.warning(
|
1251
|
+
"⚠️ No SSH host keys available (manual or auto-discovered) "
|
1252
|
+
"for %s:%d. SSH connections may fail due to host key "
|
1253
|
+
"verification. Consider setting GERRIT_KNOWN_HOSTS or ensure "
|
1254
|
+
"ssh-keyscan is available.",
|
1255
|
+
gerrit.host,
|
1256
|
+
gerrit.port,
|
1203
1257
|
)
|
1204
1258
|
return
|
1205
1259
|
|
@@ -20,7 +20,6 @@ from collections.abc import Iterable
|
|
20
20
|
from datetime import UTC
|
21
21
|
from datetime import datetime
|
22
22
|
from datetime import timedelta
|
23
|
-
from pathlib import Path
|
24
23
|
from typing import Any
|
25
24
|
|
26
25
|
from .gerrit_urls import create_gerrit_url_builder
|
@@ -205,20 +204,11 @@ class DuplicateDetector:
|
|
205
204
|
if gerrit_host and gerrit_project:
|
206
205
|
return (gerrit_host, gerrit_project)
|
207
206
|
|
208
|
-
#
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
host = self._match_first_group(r"(?m)^host=(.+)$", text)
|
214
|
-
proj = self._match_first_group(r"(?m)^project=(.+)$", text)
|
215
|
-
if host and proj:
|
216
|
-
project = proj.removesuffix(".git")
|
217
|
-
return (host.strip(), project.strip())
|
218
|
-
if host and not proj:
|
219
|
-
return (host.strip(), "")
|
220
|
-
except Exception as exc:
|
221
|
-
log.debug("Failed to read local .gitreview: %s", exc)
|
207
|
+
# Skip local .gitreview check in composite action context
|
208
|
+
# The duplicate detection runs before workspace setup, so there's no
|
209
|
+
# reliable local .gitreview file to check. Instead, rely on environment
|
210
|
+
# variables or remote fetching.
|
211
|
+
log.debug("Skipping local .gitreview check (composite action context)")
|
222
212
|
|
223
213
|
# Try to fetch .gitreview remotely (simplified version of core logic)
|
224
214
|
try:
|
@@ -13,6 +13,7 @@ from __future__ import annotations
|
|
13
13
|
|
14
14
|
import logging
|
15
15
|
import os
|
16
|
+
import shutil
|
16
17
|
import socket
|
17
18
|
from pathlib import Path
|
18
19
|
|
@@ -55,6 +56,18 @@ _MSG_UNEXPECTED_ERROR = (
|
|
55
56
|
_MSG_SAVE_FAILED = (
|
56
57
|
"Failed to save host keys to configuration file {config_file}: {error}"
|
57
58
|
)
|
59
|
+
_MSG_KEYSCAN_NOT_FOUND = (
|
60
|
+
"ssh-keyscan command not found in PATH. This tool is required for SSH "
|
61
|
+
"host key auto-discovery."
|
62
|
+
)
|
63
|
+
_MSG_KEYSCAN_CHECK_FAILED = (
|
64
|
+
"Failed to check for ssh-keyscan availability: {error}"
|
65
|
+
)
|
66
|
+
|
67
|
+
|
68
|
+
def _raise_keyscan_not_found() -> None:
|
69
|
+
"""Helper function to raise keyscan not found error."""
|
70
|
+
raise SSHDiscoveryError(_MSG_KEYSCAN_NOT_FOUND)
|
58
71
|
|
59
72
|
|
60
73
|
def is_host_reachable(hostname: str, port: int, timeout: int = 5) -> bool:
|
@@ -84,13 +97,36 @@ def fetch_ssh_host_keys(
|
|
84
97
|
Raises:
|
85
98
|
SSHDiscoveryError: If the host keys cannot be fetched
|
86
99
|
"""
|
87
|
-
log.
|
100
|
+
log.info("🔍 Starting SSH host key discovery for %s:%d", hostname, port)
|
101
|
+
|
102
|
+
# Check if ssh-keyscan is available
|
103
|
+
try:
|
104
|
+
keyscan_path = shutil.which("ssh-keyscan")
|
105
|
+
if not keyscan_path:
|
106
|
+
log.error("❌ ssh-keyscan not found in PATH")
|
107
|
+
_raise_keyscan_not_found()
|
108
|
+
log.debug("✅ Found ssh-keyscan at: %s", keyscan_path)
|
109
|
+
except Exception as exc:
|
110
|
+
log.exception("❌ Failed to check for ssh-keyscan")
|
111
|
+
raise SSHDiscoveryError(
|
112
|
+
_MSG_KEYSCAN_CHECK_FAILED.format(error=exc)
|
113
|
+
) from exc
|
114
|
+
|
115
|
+
log.debug(
|
116
|
+
"🔍 Fetching SSH host keys for %s:%d (timeout: %ds)",
|
117
|
+
hostname,
|
118
|
+
port,
|
119
|
+
timeout,
|
120
|
+
)
|
88
121
|
|
89
122
|
# First check if the host is reachable
|
123
|
+
log.debug("🔍 Testing connectivity to %s:%d...", hostname, port)
|
90
124
|
if not is_host_reachable(hostname, port, timeout=5):
|
125
|
+
log.error("❌ Host %s:%d is not reachable", hostname, port)
|
91
126
|
raise SSHDiscoveryError(
|
92
127
|
_MSG_HOST_UNREACHABLE.format(hostname=hostname, port=port)
|
93
128
|
)
|
129
|
+
log.debug("✅ Host %s:%d is reachable", hostname, port)
|
94
130
|
|
95
131
|
try:
|
96
132
|
# Use ssh-keyscan to fetch all available key types
|
@@ -105,9 +141,26 @@ def fetch_ssh_host_keys(
|
|
105
141
|
hostname,
|
106
142
|
]
|
107
143
|
|
144
|
+
log.debug("🔍 Running command: %s", " ".join(cmd))
|
108
145
|
result = run_cmd(cmd, timeout=timeout + 5)
|
109
146
|
|
147
|
+
log.debug(
|
148
|
+
"ssh-keyscan stdout length: %d chars",
|
149
|
+
len(result.stdout) if result.stdout else 0,
|
150
|
+
)
|
151
|
+
log.debug(
|
152
|
+
"ssh-keyscan stderr: %s",
|
153
|
+
result.stderr if result.stderr else "(empty)",
|
154
|
+
)
|
155
|
+
|
110
156
|
if not result.stdout or not result.stdout.strip():
|
157
|
+
log.error(
|
158
|
+
"❌ ssh-keyscan returned no output for %s:%d", hostname, port
|
159
|
+
)
|
160
|
+
log.error("Command: %s", " ".join(cmd))
|
161
|
+
log.error(
|
162
|
+
"Stderr: %s", result.stderr if result.stderr else "(none)"
|
163
|
+
)
|
111
164
|
raise SSHDiscoveryError( # noqa: TRY301
|
112
165
|
_MSG_NO_KEYS_FOUND.format(hostname=hostname, port=port)
|
113
166
|
)
|
@@ -127,20 +180,34 @@ def fetch_ssh_host_keys(
|
|
127
180
|
valid_lines.append(stripped_line)
|
128
181
|
|
129
182
|
if not valid_lines:
|
183
|
+
log.error(
|
184
|
+
"❌ No valid SSH host keys found in output for %s:%d",
|
185
|
+
hostname,
|
186
|
+
port,
|
187
|
+
)
|
188
|
+
log.error("Raw output was: %s", result.stdout)
|
130
189
|
raise SSHDiscoveryError( # noqa: TRY301
|
131
190
|
_MSG_NO_VALID_KEYS.format(hostname=hostname, port=port)
|
132
191
|
)
|
133
192
|
|
134
193
|
discovered_keys = "\n".join(valid_lines)
|
135
194
|
log.info(
|
136
|
-
"Successfully discovered %d SSH host key(s) for %s:%d",
|
195
|
+
"✅ Successfully discovered %d SSH host key(s) for %s:%d",
|
137
196
|
len(valid_lines),
|
138
197
|
hostname,
|
139
198
|
port,
|
140
199
|
)
|
141
|
-
log.
|
200
|
+
log.info("📋 Discovered keys:\n%s", discovered_keys)
|
142
201
|
|
143
202
|
except CommandError as exc:
|
203
|
+
log.exception("❌ ssh-keyscan command failed for %s:%d", hostname, port)
|
204
|
+
log.debug("Return code: %d", exc.returncode)
|
205
|
+
stdout_msg = exc.stdout if exc.stdout else "(empty)"
|
206
|
+
stderr_msg = exc.stderr if exc.stderr else "(empty)"
|
207
|
+
log.debug("Stdout: %s", stdout_msg)
|
208
|
+
log.debug("Stderr: %s", stderr_msg)
|
209
|
+
log.debug("Command: %s", " ".join(cmd))
|
210
|
+
|
144
211
|
if exc.returncode == 1:
|
145
212
|
# ssh-keyscan returns 1 when it can't connect
|
146
213
|
error_msg = exc.stderr or exc.stdout or "Connection failed"
|
@@ -381,11 +448,60 @@ def auto_discover_gerrit_host_keys(
|
|
381
448
|
)
|
382
449
|
save_to_config = False
|
383
450
|
|
451
|
+
# Log system diagnostics for debugging
|
452
|
+
|
384
453
|
log.info(
|
385
|
-
"Attempting to auto-discover SSH host keys for %s:%d"
|
454
|
+
"🔍 Attempting to auto-discover SSH host keys for %s:%d "
|
455
|
+
"(org: %s, save: %s)",
|
386
456
|
gerrit_hostname,
|
387
457
|
gerrit_port,
|
458
|
+
organization or "none",
|
459
|
+
save_to_config,
|
460
|
+
)
|
461
|
+
|
462
|
+
# System diagnostics
|
463
|
+
log.debug("🔧 System diagnostics:")
|
464
|
+
log.debug(" - OS: %s", os.name)
|
465
|
+
log.debug(
|
466
|
+
" - Platform: %s",
|
467
|
+
os.uname() if hasattr(os, "uname") else "unknown",
|
468
|
+
)
|
469
|
+
path_env = os.getenv("PATH", "not set")
|
470
|
+
path_display = (
|
471
|
+
path_env[:200] + "..." if len(path_env) > 200 else path_env
|
388
472
|
)
|
473
|
+
log.debug(" - PATH: %s", path_display)
|
474
|
+
ssh_tools = [
|
475
|
+
f"{tool}={shutil.which(tool) or 'missing'}"
|
476
|
+
for tool in [
|
477
|
+
"ssh",
|
478
|
+
"ssh-keygen",
|
479
|
+
"ssh-add",
|
480
|
+
"ssh-agent",
|
481
|
+
"ssh-keyscan",
|
482
|
+
]
|
483
|
+
]
|
484
|
+
log.debug(" - Available SSH tools: %s", ", ".join(ssh_tools))
|
485
|
+
dns_status = "checking..." if gerrit_hostname else "no hostname"
|
486
|
+
log.debug(" - Network test (DNS resolution): %s", dns_status)
|
487
|
+
|
488
|
+
# Test DNS resolution
|
489
|
+
if gerrit_hostname:
|
490
|
+
try:
|
491
|
+
import socket
|
492
|
+
|
493
|
+
addr_info = socket.getaddrinfo(gerrit_hostname, gerrit_port)
|
494
|
+
log.debug(
|
495
|
+
" - DNS resolution: ✅ %s resolves to %d addresses",
|
496
|
+
gerrit_hostname,
|
497
|
+
len(addr_info),
|
498
|
+
)
|
499
|
+
except Exception as dns_exc:
|
500
|
+
log.debug(
|
501
|
+
" - DNS resolution: ❌ %s failed: %s",
|
502
|
+
gerrit_hostname,
|
503
|
+
dns_exc,
|
504
|
+
)
|
389
505
|
|
390
506
|
# Discover the host keys
|
391
507
|
host_keys = fetch_ssh_host_keys(gerrit_hostname, gerrit_port)
|
@@ -404,12 +520,18 @@ def auto_discover_gerrit_host_keys(
|
|
404
520
|
"Set ORGANIZATION environment variable to enable auto-saving."
|
405
521
|
)
|
406
522
|
|
407
|
-
except SSHDiscoveryError
|
408
|
-
log.
|
523
|
+
except SSHDiscoveryError:
|
524
|
+
log.exception(
|
525
|
+
"❌ SSH host key auto-discovery failed for %s:%d",
|
526
|
+
gerrit_hostname,
|
527
|
+
gerrit_port,
|
528
|
+
)
|
409
529
|
return None
|
410
|
-
except Exception
|
411
|
-
log.
|
412
|
-
"Unexpected error during SSH host key auto-discovery
|
530
|
+
except Exception:
|
531
|
+
log.exception(
|
532
|
+
"❌ Unexpected error during SSH host key auto-discovery for %s:%d",
|
533
|
+
gerrit_hostname,
|
534
|
+
gerrit_port,
|
413
535
|
)
|
414
536
|
return None
|
415
537
|
else:
|
@@ -164,7 +164,10 @@ class TestActionStepValidation:
|
|
164
164
|
|
165
165
|
with_config = python_step.get("with", {})
|
166
166
|
assert "python-version-file" in with_config
|
167
|
-
assert
|
167
|
+
assert (
|
168
|
+
with_config["python-version-file"]
|
169
|
+
== "${{ github.action_path }}/pyproject.toml"
|
170
|
+
)
|
168
171
|
# No pip caching since we use uv for dependency management
|
169
172
|
assert "cache" not in with_config
|
170
173
|
|
@@ -216,7 +219,11 @@ class TestActionStepValidation:
|
|
216
219
|
|
217
220
|
script = install_step["run"]
|
218
221
|
assert "uv --version" in script
|
219
|
-
|
222
|
+
# Should contain both PyPI and local installation logic
|
223
|
+
assert "github.repository" in script
|
224
|
+
assert "modeseven-lfreleng-actions/github2gerrit-action" in script
|
225
|
+
assert "uv pip install --system ${{ github.action_path }}" in script
|
226
|
+
assert "uv pip install --system github2gerrit" in script
|
220
227
|
|
221
228
|
def test_cli_execution_step(self, action_config):
|
222
229
|
"""Test CLI execution step configuration."""
|
@@ -342,25 +342,18 @@ class TestGerritDuplicateDetection:
|
|
342
342
|
result = detector._resolve_gerrit_info_from_env_or_gitreview(gh)
|
343
343
|
assert result is None
|
344
344
|
|
345
|
-
|
346
|
-
|
347
|
-
def test_resolve_gerrit_info_from_local_gitreview(
|
348
|
-
self, mock_read_text: Any, mock_exists: Any
|
349
|
-
) -> None:
|
350
|
-
"""Test resolving Gerrit info from local .gitreview file."""
|
345
|
+
def test_resolve_gerrit_info_skips_local_gitreview(self) -> None:
|
346
|
+
"""Test that local .gitreview reading is skipped in composite action context."""
|
351
347
|
detector = DuplicateDetector(Mock())
|
352
348
|
gh = self._create_mock_github_context()
|
353
349
|
|
354
|
-
|
355
|
-
|
356
|
-
host=gerrit.example.org
|
357
|
-
port=29418
|
358
|
-
project=test/project.git
|
359
|
-
"""
|
360
|
-
|
350
|
+
# Even if a local .gitreview exists, it should be skipped
|
351
|
+
# and fall back to remote fetching or return None
|
361
352
|
with patch.dict("os.environ", {}, clear=True):
|
362
353
|
result = detector._resolve_gerrit_info_from_env_or_gitreview(gh)
|
363
|
-
assert
|
354
|
+
assert (
|
355
|
+
result is None
|
356
|
+
) # Should skip local file and return None for remote fallback
|
364
357
|
|
365
358
|
@patch("urllib.request.urlopen")
|
366
359
|
@patch("pathlib.Path.exists")
|
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
|
{github2gerrit-0.1.11 → github2gerrit-0.1.13}/docs/github2gerrit_token_permissions_classic.png
RENAMED
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
|
{github2gerrit-0.1.11 → github2gerrit-0.1.13}/src/github2gerrit/orchestrator/reconciliation.py
RENAMED
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
|
{github2gerrit-0.1.11 → github2gerrit-0.1.13}/tests/test_mapping_comment_digest_and_backref.py
RENAMED
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
|