From 1887d08814d5be7a706359ce864f98efb2626c69 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 Aug 2025 12:49:09 +0000 Subject: [PATCH 1/3] Initial plan From abce643da69a568e266b8d8bf6c7f3fb4c17dd15 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 Aug 2025 12:58:44 +0000 Subject: [PATCH 2/3] Implement ArgoCD repository credentials support for Git cloning Co-authored-by: NiklasRosenstein <1318438+NiklasRosenstein@users.noreply.github.com> --- src/nyl/generator/dispatch.py | 1 + src/nyl/generator/helmchart.py | 26 ++- src/nyl/generator/helmchart_test.py | 104 +++++++++ src/nyl/tools/argocd_repo_credentials.py | 210 +++++++++++++++++ src/nyl/tools/argocd_repo_credentials_test.py | 216 ++++++++++++++++++ 5 files changed, 556 insertions(+), 1 deletion(-) create mode 100644 src/nyl/tools/argocd_repo_credentials.py create mode 100644 src/nyl/tools/argocd_repo_credentials_test.py diff --git a/src/nyl/generator/dispatch.py b/src/nyl/generator/dispatch.py index 86df5f8e..7e4d001c 100644 --- a/src/nyl/generator/dispatch.py +++ b/src/nyl/generator/dispatch.py @@ -79,6 +79,7 @@ def default( working_dir=working_dir, kube_version=kube_version, api_versions=kube_api_versions, + client=client, ) return DispatchingGenerator( diff --git a/src/nyl/generator/helmchart.py b/src/nyl/generator/helmchart.py index 17bd03ad..f1e4e174 100644 --- a/src/nyl/generator/helmchart.py +++ b/src/nyl/generator/helmchart.py @@ -9,10 +9,16 @@ from urllib.parse import parse_qs, urlparse from loguru import logger +from kubernetes.client.api_client import ApiClient from nyl.generator import Generator from nyl.resources.helmchart import ChartRef, HelmChart, ReleaseMetadata from nyl.tools import yaml +from nyl.tools.argocd_repo_credentials import ( + apply_credential_to_git_url, + find_matching_credential, + query_argocd_repository_credentials, +) from nyl.tools.kubernetes import populate_namespace_to_resources from nyl.tools.shell import pretty_cmd from nyl.tools.types import ResourceList @@ -47,6 +53,9 @@ class HelmChartGenerator(Generator[HelmChart], resource_type=HelmChart): `--validate` flag. """ + client: ApiClient | None = None + """ Kubernetes API client for querying ArgoCD repository credentials. """ + def _materialize_chart(self, chart_ref: ChartRef) -> ChartRepositoryVersion: repository: str | None = None chart: str | None = None @@ -104,6 +113,21 @@ def _materialize_chart(self, chart_ref: ChartRef) -> ChartRepositoryVersion: # Clone the repository and find the chart in the repository. parsed = urlparse(chart_ref.git) without_query_params = parsed._replace(query="").geturl() + + # Try to get ArgoCD repository credentials for this Git URL + git_url_with_auth = without_query_params + if self.client: + try: + credentials = query_argocd_repository_credentials(self.client) + matching_credential = find_matching_credential(without_query_params, credentials) + if matching_credential: + logger.debug("Found ArgoCD repository credential for {}", without_query_params) + git_url_with_auth = apply_credential_to_git_url(without_query_params, matching_credential) + else: + logger.debug("No ArgoCD repository credential found for {}", without_query_params) + except Exception as e: + logger.warning("Failed to query ArgoCD repository credentials: {}", e) + hashed = hashlib.md5(without_query_params.encode()).hexdigest() clone_dir = self.git_repo_cache_dir / f"{hashed}-{PosixPath(parsed.path).name}" if clone_dir.exists(): @@ -112,7 +136,7 @@ def _materialize_chart(self, chart_ref: ChartRef) -> ChartRepositoryVersion: cwd = clone_dir else: logger.debug("Cloning {} to {}", without_query_params, clone_dir) - command = ["git", "clone", without_query_params, str(clone_dir)] + command = ["git", "clone", git_url_with_auth, str(clone_dir)] cwd = None subprocess.check_call(command, cwd=cwd, stdout=sys.stderr) diff --git a/src/nyl/generator/helmchart_test.py b/src/nyl/generator/helmchart_test.py index bbd9608a..4d3ebc67 100644 --- a/src/nyl/generator/helmchart_test.py +++ b/src/nyl/generator/helmchart_test.py @@ -1,12 +1,14 @@ from pathlib import Path from tempfile import TemporaryDirectory from typing import Iterator +from unittest.mock import Mock, patch import pytest from nyl.generator.helmchart import HelmChartGenerator from nyl.resources import ObjectMetadata from nyl.resources.helmchart import ChartRef, HelmChart, HelmChartSpec +from nyl.tools.argocd_repo_credentials import RepoCredential from nyl.tools.testing import TESTDATA_PATH CHART_PATH = TESTDATA_PATH / "helm-test-chart" @@ -19,6 +21,15 @@ def generator() -> Iterator[HelmChartGenerator]: yield HelmChartGenerator(tmp, tmp, [], tmp, "1.31", set()) +@pytest.fixture +def generator_with_client() -> Iterator[HelmChartGenerator]: + """Generator with a mock Kubernetes client for testing ArgoCD credentials.""" + with TemporaryDirectory() as _tmp: + tmp = Path(_tmp) + mock_client = Mock() + yield HelmChartGenerator(tmp, tmp, [], tmp, "1.31", set(), client=mock_client) + + def test_helmchart_resource_overrides_labels(generator: HelmChartGenerator) -> None: """ This test verifies that resources generated by a Helm chart component have their labels updated and overwritten @@ -43,3 +54,96 @@ def test_helmchart_resource_overrides_labels(generator: HelmChartGenerator) -> N "app.kubernetes.io/version": "1.0.0", "team": "testing", } + + +def test_helmchart_git_with_argocd_credentials(generator_with_client: HelmChartGenerator) -> None: + """ + Test that HelmChart with git source uses ArgoCD repository credentials when available. + """ + # Mock the credential query to return a test credential + test_credential = RepoCredential( + url="https://github.com/myorg/", + username="testuser", + password="testtoken" + ) + + with patch('nyl.generator.helmchart.query_argocd_repository_credentials') as mock_query, \ + patch('nyl.generator.helmchart.find_matching_credential') as mock_find, \ + patch('nyl.generator.helmchart.apply_credential_to_git_url') as mock_apply, \ + patch('nyl.generator.helmchart.subprocess.check_call') as mock_subprocess: + + # Setup mocks + mock_query.return_value = [test_credential] + mock_find.return_value = test_credential + mock_apply.return_value = "https://testuser:testtoken@github.com/myorg/test-repo.git" + + # Create chart reference with Git URL + chart_ref = ChartRef(git="https://github.com/myorg/test-repo.git") + + # Call the _materialize_chart method + generator_with_client._materialize_chart(chart_ref) + + # Verify that ArgoCD credentials were queried + mock_query.assert_called_once_with(generator_with_client.client) + + # Verify that credential matching was attempted + mock_find.assert_called_once_with("https://github.com/myorg/test-repo.git", [test_credential]) + + # Verify that credentials were applied to the URL + mock_apply.assert_called_once_with("https://github.com/myorg/test-repo.git", test_credential) + + # Verify that git clone was called with the authenticated URL + mock_subprocess.assert_called() + clone_call = mock_subprocess.call_args_list[0] + assert "https://testuser:testtoken@github.com/myorg/test-repo.git" in clone_call[0][0] + + +def test_helmchart_git_without_argocd_credentials(generator_with_client: HelmChartGenerator) -> None: + """ + Test that HelmChart with git source falls back to original URL when no ArgoCD credentials are found. + """ + with patch('nyl.generator.helmchart.query_argocd_repository_credentials') as mock_query, \ + patch('nyl.generator.helmchart.find_matching_credential') as mock_find, \ + patch('nyl.generator.helmchart.subprocess.check_call') as mock_subprocess: + + # Setup mocks - no credentials found + mock_query.return_value = [] + mock_find.return_value = None + + # Create chart reference with Git URL + chart_ref = ChartRef(git="https://github.com/myorg/test-repo.git") + + # Call the _materialize_chart method + generator_with_client._materialize_chart(chart_ref) + + # Verify that ArgoCD credentials were queried + mock_query.assert_called_once_with(generator_with_client.client) + + # Verify that credential matching was attempted + mock_find.assert_called_once_with("https://github.com/myorg/test-repo.git", []) + + # Verify that git clone was called with the original URL + mock_subprocess.assert_called() + clone_call = mock_subprocess.call_args_list[0] + assert "https://github.com/myorg/test-repo.git" in clone_call[0][0] + + +def test_helmchart_git_without_client() -> None: + """ + Test that HelmChart with git source works without Kubernetes client (no ArgoCD credential lookup). + """ + with TemporaryDirectory() as _tmp: + tmp = Path(_tmp) + generator_no_client = HelmChartGenerator(tmp, tmp, [], tmp, "1.31", set(), client=None) + + with patch('nyl.generator.helmchart.subprocess.check_call') as mock_subprocess: + # Create chart reference with Git URL + chart_ref = ChartRef(git="https://github.com/myorg/test-repo.git") + + # Call the _materialize_chart method + generator_no_client._materialize_chart(chart_ref) + + # Verify that git clone was called with the original URL + mock_subprocess.assert_called() + clone_call = mock_subprocess.call_args_list[0] + assert "https://github.com/myorg/test-repo.git" in clone_call[0][0] diff --git a/src/nyl/tools/argocd_repo_credentials.py b/src/nyl/tools/argocd_repo_credentials.py new file mode 100644 index 00000000..8ea0303d --- /dev/null +++ b/src/nyl/tools/argocd_repo_credentials.py @@ -0,0 +1,210 @@ +""" +ArgoCD repository credentials support for Git operations. + +This module provides functionality to query ArgoCD repository credentials from Kubernetes +and apply them to Git operations for seamless authentication. +""" +import base64 +from dataclasses import dataclass +from typing import Any +from urllib.parse import urlparse + +from kubernetes.client.api_client import ApiClient +from kubernetes.client.rest import ApiException +from kubernetes.dynamic import DynamicClient +from loguru import logger + + +@dataclass +class RepoCredential: + """Represents repository credentials from ArgoCD.""" + + url: str + username: str | None = None + password: str | None = None + ssh_private_key: str | None = None + insecure: bool = False + enable_lfs: bool = False + type: str = "git" + + @property + def is_ssh(self) -> bool: + """Check if this credential uses SSH authentication.""" + return self.ssh_private_key is not None + + @property + def is_https(self) -> bool: + """Check if this credential uses HTTPS authentication.""" + return self.username is not None and self.password is not None + + +def query_argocd_repository_credentials(client: ApiClient, namespace: str = "argocd") -> list[RepoCredential]: + """ + Query ArgoCD repository credentials from Kubernetes secrets. + + Args: + client: Kubernetes API client + namespace: Namespace where ArgoCD secrets are stored (default: "argocd") + + Returns: + List of repository credentials found + """ + dynamic_client = DynamicClient(client) + + try: + # Get the Secret resource + secret_resource = dynamic_client.resources.get(api_version="v1", kind="Secret") + + # Query secrets with ArgoCD repository labels + secrets = secret_resource.get(namespace=namespace, label_selector="argocd.argoproj.io/secret-type in (repository,repo-creds)") + + credentials = [] + + for secret in secrets.items: + try: + cred = _parse_secret_to_credential(secret) + if cred: + credentials.append(cred) + except Exception as e: + logger.warning(f"Failed to parse ArgoCD secret {secret.metadata.name}: {e}") + + logger.debug(f"Found {len(credentials)} ArgoCD repository credentials in namespace {namespace}") + return credentials + + except ApiException as e: + if e.status == 404: + logger.debug(f"No ArgoCD secrets found in namespace {namespace}") + return [] + else: + logger.warning(f"Failed to query ArgoCD repository credentials: {e}") + return [] + + +def _parse_secret_to_credential(secret: Any) -> RepoCredential | None: + """Parse a Kubernetes secret into a RepoCredential.""" + data = secret.data or {} + + # Get URL which is required + url = _decode_secret_data(data, "url") + if not url: + return None + + # Extract other fields + username = _decode_secret_data(data, "username") + password = _decode_secret_data(data, "password") + ssh_private_key = _decode_secret_data(data, "sshPrivateKey") + insecure = _decode_secret_data(data, "insecure") == "true" + enable_lfs = _decode_secret_data(data, "enableLfs") == "true" + repo_type = _decode_secret_data(data, "type") or "git" + + return RepoCredential( + url=url, + username=username, + password=password, + ssh_private_key=ssh_private_key, + insecure=insecure, + enable_lfs=enable_lfs, + type=repo_type, + ) + + +def _decode_secret_data(data: dict[str, str], key: str) -> str | None: + """Decode base64 secret data field.""" + if key not in data: + return None + + try: + return base64.b64decode(data[key]).decode("utf-8") + except Exception: + return None + + +def find_matching_credential(git_url: str, credentials: list[RepoCredential]) -> RepoCredential | None: + """ + Find the best matching credential for a Git URL. + + This implements ArgoCD's credential matching logic: + 1. Exact URL match (from 'repository' secrets) + 2. Prefix URL match (from 'repo-creds' credential templates) + + Args: + git_url: The Git repository URL to find credentials for + credentials: List of available repository credentials + + Returns: + The best matching credential, or None if no match found + """ + if not credentials: + return None + + # Normalize the input URL for comparison + normalized_url = _normalize_git_url(git_url) + + # First pass: look for exact matches + for cred in credentials: + if _normalize_git_url(cred.url) == normalized_url: + logger.debug(f"Found exact credential match for {git_url}") + return cred + + # Second pass: look for prefix matches (credential templates) + best_match = None + best_match_length = 0 + + for cred in credentials: + cred_url_normalized = _normalize_git_url(cred.url) + if normalized_url.startswith(cred_url_normalized): + if len(cred_url_normalized) > best_match_length: + best_match = cred + best_match_length = len(cred_url_normalized) + + if best_match: + logger.debug(f"Found prefix credential match for {git_url}") + + return best_match + + +def _normalize_git_url(url: str) -> str: + """ + Normalize a Git URL for comparison. + + This removes trailing slashes and ensures consistent format for matching. + """ + # Handle SSH URLs (git@host:path format) + if "@" in url and "://" not in url: + return url.rstrip("/") + + # Handle HTTPS/HTTP URLs + parsed = urlparse(url) + if parsed.scheme in ("http", "https"): + # Reconstruct without query/fragment and normalize path + path = parsed.path.rstrip("/") + return f"{parsed.scheme}://{parsed.netloc}{path}" + + # For other formats, just remove trailing slash + return url.rstrip("/") + + +def apply_credential_to_git_url(git_url: str, credential: RepoCredential) -> str: + """ + Apply repository credential to a Git URL for authentication. + + For HTTPS URLs with username/password, embeds the credentials in the URL. + For SSH URLs, returns the original URL (SSH key setup is handled separately). + + Args: + git_url: Original Git repository URL + credential: Repository credential to apply + + Returns: + Git URL with authentication applied (for HTTPS) or original URL (for SSH) + """ + if credential.is_https: + # For HTTPS authentication, embed credentials in URL + parsed = urlparse(git_url) + if parsed.scheme in ("http", "https"): + # Construct URL with embedded credentials + netloc = f"{credential.username}:{credential.password}@{parsed.netloc}" + return f"{parsed.scheme}://{netloc}{parsed.path}" + + # For SSH or if no HTTPS credentials, return original URL + return git_url \ No newline at end of file diff --git a/src/nyl/tools/argocd_repo_credentials_test.py b/src/nyl/tools/argocd_repo_credentials_test.py new file mode 100644 index 00000000..57bc6f7b --- /dev/null +++ b/src/nyl/tools/argocd_repo_credentials_test.py @@ -0,0 +1,216 @@ +"""Tests for ArgoCD repository credentials functionality.""" +import base64 +from unittest.mock import Mock, patch + +import pytest + +from nyl.tools.argocd_repo_credentials import ( + RepoCredential, + apply_credential_to_git_url, + find_matching_credential, + query_argocd_repository_credentials, + _normalize_git_url, + _parse_secret_to_credential, +) + + +def test_repo_credential_properties(): + """Test RepoCredential property methods.""" + # SSH credential + ssh_cred = RepoCredential( + url="git@github.com:myorg/repo.git", + ssh_private_key="-----BEGIN RSA PRIVATE KEY-----\n..." + ) + assert ssh_cred.is_ssh + assert not ssh_cred.is_https + + # HTTPS credential + https_cred = RepoCredential( + url="https://github.com/myorg/repo.git", + username="user", + password="token" + ) + assert not https_cred.is_ssh + assert https_cred.is_https + + # No credentials + no_cred = RepoCredential(url="https://github.com/myorg/repo.git") + assert not no_cred.is_ssh + assert not no_cred.is_https + + +def test_normalize_git_url(): + """Test Git URL normalization.""" + # HTTPS URLs + assert _normalize_git_url("https://github.com/myorg/repo.git") == "https://github.com/myorg/repo.git" + assert _normalize_git_url("https://github.com/myorg/repo.git/") == "https://github.com/myorg/repo.git" + assert _normalize_git_url("https://github.com/myorg/") == "https://github.com/myorg" + + # SSH URLs + assert _normalize_git_url("git@github.com:myorg/repo.git") == "git@github.com:myorg/repo.git" + assert _normalize_git_url("git@github.com:myorg/repo.git/") == "git@github.com:myorg/repo.git" + + # Other formats + assert _normalize_git_url("file:///path/to/repo/") == "file:///path/to/repo" + + +def test_find_matching_credential(): + """Test credential matching logic.""" + credentials = [ + # Exact match for specific repo + RepoCredential( + url="https://github.com/myorg/specific-repo.git", + username="user1", + password="pass1" + ), + # Prefix match for all repos in org + RepoCredential( + url="https://github.com/myorg/", + username="user2", + password="pass2" + ), + # Broader prefix match for all GitHub + RepoCredential( + url="https://github.com/", + username="user3", + password="pass3" + ), + ] + + # Test exact match takes precedence + match = find_matching_credential("https://github.com/myorg/specific-repo.git", credentials) + assert match is not None + assert match.username == "user1" + + # Test prefix match for other repo in same org + match = find_matching_credential("https://github.com/myorg/other-repo.git", credentials) + assert match is not None + assert match.username == "user2" + + # Test broader prefix match + match = find_matching_credential("https://github.com/otherorg/repo.git", credentials) + assert match is not None + assert match.username == "user3" + + # Test no match + match = find_matching_credential("https://gitlab.com/myorg/repo.git", credentials) + assert match is None + + # Test with trailing slashes + match = find_matching_credential("https://github.com/myorg/specific-repo.git/", credentials) + assert match is not None + assert match.username == "user1" + + +def test_apply_credential_to_git_url(): + """Test applying credentials to Git URLs.""" + # HTTPS credential + https_cred = RepoCredential( + url="https://github.com/myorg/", + username="user", + password="token123" + ) + + result = apply_credential_to_git_url("https://github.com/myorg/repo.git", https_cred) + assert result == "https://user:token123@github.com/myorg/repo.git" + + # SSH credential (should return original URL) + ssh_cred = RepoCredential( + url="git@github.com:myorg/", + ssh_private_key="-----BEGIN RSA PRIVATE KEY-----\n..." + ) + + result = apply_credential_to_git_url("git@github.com:myorg/repo.git", ssh_cred) + assert result == "git@github.com:myorg/repo.git" + + # No credentials (should return original URL) + no_cred = RepoCredential(url="https://github.com/myorg/") + result = apply_credential_to_git_url("https://github.com/myorg/repo.git", no_cred) + assert result == "https://github.com/myorg/repo.git" + + +def test_parse_secret_to_credential(): + """Test parsing Kubernetes secret to RepoCredential.""" + # Mock secret with HTTPS credentials + secret_data = { + "url": base64.b64encode(b"https://github.com/myorg/repo.git").decode(), + "username": base64.b64encode(b"user").decode(), + "password": base64.b64encode(b"token").decode(), + "type": base64.b64encode(b"git").decode(), + } + + mock_secret = Mock() + mock_secret.data = secret_data + + cred = _parse_secret_to_credential(mock_secret) + assert cred is not None + assert cred.url == "https://github.com/myorg/repo.git" + assert cred.username == "user" + assert cred.password == "token" + assert cred.type == "git" + assert cred.is_https + assert not cred.is_ssh + + # Mock secret with SSH credentials + secret_data_ssh = { + "url": base64.b64encode(b"git@github.com:myorg/repo.git").decode(), + "sshPrivateKey": base64.b64encode(b"-----BEGIN RSA PRIVATE KEY-----\nkey_content\n-----END RSA PRIVATE KEY-----").decode(), + } + + mock_secret_ssh = Mock() + mock_secret_ssh.data = secret_data_ssh + + cred_ssh = _parse_secret_to_credential(mock_secret_ssh) + assert cred_ssh is not None + assert cred_ssh.url == "git@github.com:myorg/repo.git" + assert cred_ssh.ssh_private_key == "-----BEGIN RSA PRIVATE KEY-----\nkey_content\n-----END RSA PRIVATE KEY-----" + assert cred_ssh.is_ssh + assert not cred_ssh.is_https + + # Mock secret without URL (should return None) + mock_secret_no_url = Mock() + mock_secret_no_url.data = {"username": base64.b64encode(b"user").decode()} + + cred_no_url = _parse_secret_to_credential(mock_secret_no_url) + assert cred_no_url is None + + +def test_query_argocd_repository_credentials(): + """Test querying ArgoCD repository credentials from Kubernetes.""" + # Mock Kubernetes client and dynamic client + mock_client = Mock() + mock_dynamic_client = Mock() + mock_secret_resource = Mock() + + # Mock secret data + mock_secrets = Mock() + mock_secrets.items = [ + Mock( + metadata=Mock(name="github-creds"), + data={ + "url": base64.b64encode(b"https://github.com/myorg/").decode(), + "username": base64.b64encode(b"user").decode(), + "password": base64.b64encode(b"token").decode(), + } + ) + ] + + mock_secret_resource.get.return_value = mock_secrets + mock_dynamic_client.resources.get.return_value = mock_secret_resource + + # Mock the DynamicClient constructor + with patch('nyl.tools.argocd_repo_credentials.DynamicClient', return_value=mock_dynamic_client): + credentials = query_argocd_repository_credentials(mock_client) + + # Verify the correct API calls were made + mock_dynamic_client.resources.get.assert_called_once_with(api_version="v1", kind="Secret") + mock_secret_resource.get.assert_called_once_with( + namespace="argocd", + label_selector="argocd.argoproj.io/secret-type in (repository,repo-creds)" + ) + + # Verify credentials were parsed correctly + assert len(credentials) == 1 + assert credentials[0].url == "https://github.com/myorg/" + assert credentials[0].username == "user" + assert credentials[0].password == "token" \ No newline at end of file From 74ba77200b0f4dee5d688c75d42dc73557ab057c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 Aug 2025 13:01:26 +0000 Subject: [PATCH 3/3] Improve ArgoCD credentials documentation and SSH key handling notes Co-authored-by: NiklasRosenstein <1318438+NiklasRosenstein@users.noreply.github.com> --- src/nyl/generator/helmchart.py | 11 +++++++++-- src/nyl/tools/argocd_repo_credentials.py | 10 +++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/nyl/generator/helmchart.py b/src/nyl/generator/helmchart.py index f1e4e174..35f8ac07 100644 --- a/src/nyl/generator/helmchart.py +++ b/src/nyl/generator/helmchart.py @@ -121,8 +121,15 @@ def _materialize_chart(self, chart_ref: ChartRef) -> ChartRepositoryVersion: credentials = query_argocd_repository_credentials(self.client) matching_credential = find_matching_credential(without_query_params, credentials) if matching_credential: - logger.debug("Found ArgoCD repository credential for {}", without_query_params) - git_url_with_auth = apply_credential_to_git_url(without_query_params, matching_credential) + if matching_credential.is_https: + logger.debug("Using ArgoCD HTTPS repository credential for {}", without_query_params) + git_url_with_auth = apply_credential_to_git_url(without_query_params, matching_credential) + elif matching_credential.is_ssh: + logger.info("ArgoCD SSH repository credential found for {} but SSH key authentication " + "is not fully implemented. Using original URL.", without_query_params) + else: + logger.debug("ArgoCD repository credential found for {} but no authentication method available", + without_query_params) else: logger.debug("No ArgoCD repository credential found for {}", without_query_params) except Exception as e: diff --git a/src/nyl/tools/argocd_repo_credentials.py b/src/nyl/tools/argocd_repo_credentials.py index 8ea0303d..0d6d3299 100644 --- a/src/nyl/tools/argocd_repo_credentials.py +++ b/src/nyl/tools/argocd_repo_credentials.py @@ -189,7 +189,12 @@ def apply_credential_to_git_url(git_url: str, credential: RepoCredential) -> str Apply repository credential to a Git URL for authentication. For HTTPS URLs with username/password, embeds the credentials in the URL. - For SSH URLs, returns the original URL (SSH key setup is handled separately). + For SSH URLs, returns the original URL (SSH key setup would require additional + configuration outside the scope of URL modification). + + Note: SSH private key authentication is detected but not fully implemented. + The SSH private key would need to be written to a temporary file and configured + with Git via GIT_SSH_COMMAND or ssh-agent, which is not currently supported. Args: git_url: Original Git repository URL @@ -205,6 +210,9 @@ def apply_credential_to_git_url(git_url: str, credential: RepoCredential) -> str # Construct URL with embedded credentials netloc = f"{credential.username}:{credential.password}@{parsed.netloc}" return f"{parsed.scheme}://{netloc}{parsed.path}" + elif credential.is_ssh: + logger.debug("SSH private key authentication detected but not fully implemented. " + "Using original URL - SSH key setup would require additional Git configuration.") # For SSH or if no HTTPS credentials, return original URL return git_url \ No newline at end of file