From 9ef1fb1e3a6610987432da0bcb4c38fd507f8e3a Mon Sep 17 00:00:00 2001 From: Tobias Genannt Date: Sat, 1 Apr 2023 15:35:11 +0200 Subject: [PATCH] Use dulwich as Git client --- base_requirements.txt | 4 ++++ netbox/core/data_backends.py | 39 ++++++++++++------------------------ requirements.txt | 1 + 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/base_requirements.txt b/base_requirements.txt index cf81a3350..4e7d1d296 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -74,6 +74,10 @@ drf-spectacular # https://github.com/tfranzel/drf-spectacular-sidecar drf-spectacular-sidecar +# Git client for file sync +# https://github.com/jelmer/dulwich/releases +dulwich + # RSS feed parser # https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst feedparser diff --git a/netbox/core/data_backends.py b/netbox/core/data_backends.py index 8fb89372f..d8424c223 100644 --- a/netbox/core/data_backends.py +++ b/netbox/core/data_backends.py @@ -1,17 +1,18 @@ import logging import os import re -import subprocess import tempfile from contextlib import contextmanager from pathlib import Path -from urllib.parse import quote, urlunparse, urlparse +from urllib.parse import urlparse import boto3 from botocore.config import Config as Boto3Config from django import forms from django.conf import settings from django.utils.translation import gettext as _ +from dulwich import porcelain +from dulwich.config import StackedConfig from netbox.registry import registry from .choices import DataSourceTypeChoices @@ -88,37 +89,23 @@ class GitBackend(DataBackend): def fetch(self): local_path = tempfile.TemporaryDirectory() - # Add authentication credentials to URL (if specified) username = self.params.get('username') password = self.params.get('password') - if username and password: - # Add username & password to URL - parsed = urlparse(self.url) - url = f'{parsed.scheme}://{quote(username)}:{quote(password)}@{parsed.netloc}{parsed.path}' - else: - url = self.url + branch = self.params.get('branch') + config = StackedConfig.default() - # Compile git arguments - args = [settings.GIT_PATH, 'clone', '--depth', '1'] - if branch := self.params.get('branch'): - args.extend(['--branch', branch]) - args.extend([url, local_path.name]) - - # Prep environment variables - env_vars = {} if settings.HTTP_PROXIES and self.url_scheme in ('http', 'https'): - env_vars['http_proxy'] = settings.HTTP_PROXIES.get(self.url_scheme) + if proxy := settings.HTTP_PROXIES.get(self.url_scheme): + config.set("http", "proxy", proxy) - logger.debug(f"Cloning git repo: {' '.join(args)}") + logger.debug(f"Cloning git repo: {self.url}") try: - subprocess.run(args, check=True, capture_output=True, env=env_vars) - except FileNotFoundError as e: - raise SyncError( - f"Unable to fetch: git executable not found. Check that the git executable exists at the " - f"configured path: {settings.GIT_PATH}" + porcelain.clone( + self.url, local_path.name, depth=1, branch=branch, username=username, password=password, + config=config, quiet=True, errstream=porcelain.NoneStream() ) - except subprocess.CalledProcessError as e: - raise SyncError(f"Fetching remote data failed: {e.stderr}") + except BaseException as e: + raise SyncError(f"Fetching remote data failed ({type(e).__name__}): {e}") yield local_path.name diff --git a/requirements.txt b/requirements.txt index 29665647f..5a9ed3a3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,7 @@ django-timezone-field==5.0 djangorestframework==3.14.0 drf-spectacular==0.26.1 drf-spectacular-sidecar==2023.4.1 +dulwich==0.21.3 feedparser==6.0.10 graphene-django==3.0.0 gunicorn==20.1.0