Jelajahi Sumber

Use dulwich as Git client

Tobias Genannt 2 tahun lalu
induk
melakukan
9ef1fb1e3a
3 mengubah file dengan 19 tambahan dan 27 penghapusan
  1. 4 0
      base_requirements.txt
  2. 14 27
      netbox/core/data_backends.py
  3. 1 0
      requirements.txt

+ 4 - 0
base_requirements.txt

@@ -74,6 +74,10 @@ drf-spectacular
 # https://github.com/tfranzel/drf-spectacular-sidecar
 # https://github.com/tfranzel/drf-spectacular-sidecar
 drf-spectacular-sidecar
 drf-spectacular-sidecar
 
 
+# Git client for file sync
+# https://github.com/jelmer/dulwich/releases
+dulwich
+
 # RSS feed parser
 # RSS feed parser
 # https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst
 # https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst
 feedparser
 feedparser

+ 14 - 27
netbox/core/data_backends.py

@@ -1,17 +1,18 @@
 import logging
 import logging
 import os
 import os
 import re
 import re
-import subprocess
 import tempfile
 import tempfile
 from contextlib import contextmanager
 from contextlib import contextmanager
 from pathlib import Path
 from pathlib import Path
-from urllib.parse import quote, urlunparse, urlparse
+from urllib.parse import urlparse
 
 
 import boto3
 import boto3
 from botocore.config import Config as Boto3Config
 from botocore.config import Config as Boto3Config
 from django import forms
 from django import forms
 from django.conf import settings
 from django.conf import settings
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _
+from dulwich import porcelain
+from dulwich.config import StackedConfig
 
 
 from netbox.registry import registry
 from netbox.registry import registry
 from .choices import DataSourceTypeChoices
 from .choices import DataSourceTypeChoices
@@ -88,37 +89,23 @@ class GitBackend(DataBackend):
     def fetch(self):
     def fetch(self):
         local_path = tempfile.TemporaryDirectory()
         local_path = tempfile.TemporaryDirectory()
 
 
-        # Add authentication credentials to URL (if specified)
         username = self.params.get('username')
         username = self.params.get('username')
         password = self.params.get('password')
         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
-
-        # 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 = {}
+        branch = self.params.get('branch')
+        config = StackedConfig.default()
+
         if settings.HTTP_PROXIES and self.url_scheme in ('http', 'https'):
         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:
         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
         yield local_path.name
 
 

+ 1 - 0
requirements.txt

@@ -17,6 +17,7 @@ django-timezone-field==5.0
 djangorestframework==3.14.0
 djangorestframework==3.14.0
 drf-spectacular==0.26.1
 drf-spectacular==0.26.1
 drf-spectacular-sidecar==2023.4.1
 drf-spectacular-sidecar==2023.4.1
+dulwich==0.21.3
 feedparser==6.0.10
 feedparser==6.0.10
 graphene-django==3.0.0
 graphene-django==3.0.0
 gunicorn==20.1.0
 gunicorn==20.1.0