diff --git a/repo_cloner/lib/__init__.py b/repo_cloner/lib/__init__.py index 4af3cf5..bea8440 100644 --- a/repo_cloner/lib/__init__.py +++ b/repo_cloner/lib/__init__.py @@ -1,5 +1,5 @@ from .checksum import gen_repo_hashed_name -from .cred_helper import prepare_git_auth +from .cred_helper import prepare_git_auth, init_gh_token from .cloner_config import ClonerConfig, ClonerConfigParser from .config_file_not_found_error import ConfigFileNotFoundError from .default_cloner_config import DefaultClonerConfig diff --git a/repo_cloner/lib/cred_helper.py b/repo_cloner/lib/cred_helper.py index 2e8df24..58ff5d9 100644 --- a/repo_cloner/lib/cred_helper.py +++ b/repo_cloner/lib/cred_helper.py @@ -1,9 +1,40 @@ from git import Repo, GitConfigParser import logging import os +from typing import Optional, List +from pathlib import Path log = logging.getLogger("rc.cfghelper") +token: Optional[str] = None + + +def gen_gh_token_candidates() -> List[Path]: + return [ + Path(os.getcwd()).joinpath(".gh-token"), + Path(os.getenv("HOME")).joinpath(".config", "gh-token"), + Path("/etc").joinpath("cloner-gh-token"), + ] + + +def init_gh_token(): + for candidate in gen_gh_token_candidates(): + log.debug(f"Loading gh-candidate candidate {candidate.as_posix()}") + load_gh_token(candidate) + if token: + log.info(f"Token succesfully loaded") + break + + +def load_gh_token(path: Path): + global token + log.info(f"Loading secret github token") + if not path.is_file(): + log.warning(f"Token load did not pass - file not found") + return + # load token + token = path.read_text().strip() + def config_try_override(config_writer: GitConfigParser, section: str, option: str, value: str): if section not in config_writer.sections(): @@ -32,8 +63,20 @@ def prepare_git_auth(repo: str, config_dir): ssh_identity: str = os.path.join(config_dir, "ssh", "identity") with repo.config_writer("user") as cfgw: + # github personal token + # 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 + if token: + config_try_override( + cfgw, + f"url \"https://{token}:x-oauth-basic@github.com/\"", + "insteadOf", + "https://github.com/" + ) + + # https credential store log.debug(f"Writing credential store setting") config_try_override(cfgw, "credential", "helper", f"store --file={cred_store}") + # ssh key log.debug(f"Writing SSH cert path") config_try_override( cfgw, diff --git a/repo_cloner/lib/repo_tool.py b/repo_cloner/lib/repo_tool.py index 1bfb4a9..a0acb6f 100644 --- a/repo_cloner/lib/repo_tool.py +++ b/repo_cloner/lib/repo_tool.py @@ -194,7 +194,7 @@ class RepoTool: self._last_fetch_data = remote.fetch( ["+refs/heads/*:refs/heads/*", "+refs/tags/*:refs/tags/*"], progress = GitRemoteProgress(), - kill_after_timeout = 60, + kill_after_timeout = 600, prune = True ) log.debug("Fetch finished!") diff --git a/tests/lib/test_cred_helper.py b/tests/lib/test_cred_helper.py new file mode 100644 index 0000000..f6721a7 --- /dev/null +++ b/tests/lib/test_cred_helper.py @@ -0,0 +1,102 @@ +import pytest +from repo_cloner.lib.cred_helper import * +from repo_cloner.lib import cred_helper +import os +from unittest.mock import MagicMock, patch +from cloner_test_fixtures import path_repo_base, support_data_path +from git import Repo + + +def test_gen_gh_token_candidates(monkeypatch): + with monkeypatch.context() as mp: + mp.setenv("HOME", "/mocked/home") + candidates = gen_gh_token_candidates() + assert candidates[0].as_posix() == os.path.join(os.getcwd(), ".gh-token") + assert candidates[1].as_posix() == "/mocked/home/.config/gh-token" + assert candidates[2].as_posix() == "/etc/cloner-gh-token" + assert len(candidates) == 3 + + +def test_load_gh_token(tmp_path): + # no token + token_file = tmp_path.joinpath("gh-token") + load_gh_token(token_file) + assert cred_helper.token is None + + token_file.write_text("test-token\n") + load_gh_token(token_file) + assert cred_helper.token == "test-token" + + +def test_init_gh_token(tmp_path, monkeypatch, caplog): + with patch("repo_cloner.lib.cred_helper.load_gh_token", autospec = True) as p: + cred_helper.token = None + init_gh_token() + assert p.call_count == 3 + + # "loaded" token + with monkeypatch.context() as mp: + mp.setattr(os, "getcwd", lambda: tmp_path.as_posix()) + tmp_path.joinpath(".gh-token").write_text("testtoken") + caplog.set_level(0) + init_gh_token() + assert "Token succesfully loaded" in caplog.text + + +def test_config_try_override(tmp_path, path_repo_base, monkeypatch): + tmp_path.joinpath("git").mkdir() + with monkeypatch.context() as mp: + mp.setenv("XDG_CONFIG_HOME", tmp_path.as_posix()) + r = Repo(path_repo_base) + cfgw = r.config_writer("user") + + config_try_override(cfgw, "user", "name", "Mocker") + assert cfgw.get("user", "name") == "Mocker" + + config_try_override(cfgw, "user", "name", "Loser") + assert cfgw.get("user", "name") == "Loser" + + x = tmp_path.joinpath("git", "config") + assert x.is_file() + x = x.read_text() + assert x == "[user]\n name = Loser\n" + + +def test_prepare_git_auth(tmp_path, path_repo_base, monkeypatch): + tmp_path.joinpath("git").mkdir() + cred_helper.token = None + + with monkeypatch.context() as mp: + mp.setenv("XDG_CONFIG_HOME", tmp_path.as_posix()) + + prepare_git_auth(path_repo_base.as_posix(), tmp_path.as_posix()) + + x = tmp_path.joinpath("git", "config") + assert x.exists() + config = x.read_text() + assert config == \ + f"[credential]\n" \ + f" helper = store --file={tmp_path.as_posix()}/git/git-credentials\n" \ + "[core]\n" \ + f" sshcommand = ssh -i {tmp_path.as_posix()}/ssh/identity -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes -q\n" + + +def test_prepare_git_auth_token(tmp_path, path_repo_base, monkeypatch): + # tmp_path.joinpath("git").mkdir() + + with monkeypatch.context() as mp: + mp.setenv("XDG_CONFIG_HOME", tmp_path.as_posix()) + cred_helper.token = "token123" + + prepare_git_auth(path_repo_base.as_posix(), tmp_path.as_posix()) + + x = tmp_path.joinpath("git", "config") + assert x.exists() + config = x.read_text() + assert config == \ + "[url \"https://token123:x-oauth-basic@github.com/\"]\n" \ + " insteadOf = https://github.com/\n" \ + f"[credential]\n" \ + f" helper = store --file={tmp_path.as_posix()}/git/git-credentials\n" \ + "[core]\n" \ + f" sshcommand = ssh -i {tmp_path.as_posix()}/ssh/identity -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes -q\n"