prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Datasources stuck in sync when using git + ssh from ./manage.py syncdatasource
  ### NetBox version
  
  v3.6.1
  
  ### Python version
  
  3.11
  
  ### Steps to Reproduce
  
  In Data Sources
  Add
  Name: test
  Type: git
  URL: git@github.com:netbox-community/netbox.git
  Create
  
  docker compose exec netbox ./manage.py syncdatasource test
  
  
  
  
  
  ### Expected Behavior
  
  Usually leads to some sort of ssh question or failure, and I would expect the exception to set the status to failed, and then be able to hit sync again.
  
  I'm not sure exactly how NetBox works, but looking at one of the exceptions...
  core.exceptions.SyncError: Fetching remote data failed (HangupException): 
  
  class SyncError(Exception):
      pass
  
  Does this mean the status is not being reset correctly due to the status being left as syncing?
  
  
  
  ### Observed Behavior
  
  datasource.status = syncing in nbshell
  'syncing' in gui
  Sync option is now greyed out and cannot reset status without manually setting it in nbshell:
  
  for d in DataSource.objects.filter(status='syncing'):
    d.status = 'failed'
    d.save()
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `netbox/core/management/commands/syncdatasource.py`
  Content:
  ```
  1 from django.core.management.base import BaseCommand, CommandError
  2 
  3 from core.models import DataSource
  4 
  5 
  6 class Command(BaseCommand):
  7     help = "Synchronize a data source from its remote upstream"
  8 
  9     def add_arguments(self, parser):
  10         parser.add_argument('name', nargs='*', help="Data source(s) to synchronize")
  11         parser.add_argument(
  12             "--all", action='store_true', dest='sync_all',
  13             help="Synchronize all data sources"
  14         )
  15 
  16     def handle(self, *args, **options):
  17 
  18         # Find DataSources to sync
  19         if options['sync_all']:
  20             datasources = DataSource.objects.all()
  21         elif options['name']:
  22             datasources = DataSource.objects.filter(name__in=options['name'])
  23             # Check for invalid names
  24             found_names = {ds['name'] for ds in datasources.values('name')}
  25             if invalid_names := set(options['name']) - found_names:
  26                 raise CommandError(f"Invalid data source names: {', '.join(invalid_names)}")
  27         else:
  28             raise CommandError(f"Must specify at least one data source, or set --all.")
  29 
  30         if len(options['name']) > 1:
  31             self.stdout.write(f"Syncing {len(datasources)} data sources.")
  32 
  33         for i, datasource in enumerate(datasources, start=1):
  34             self.stdout.write(f"[{i}] Syncing {datasource}... ", ending='')
  35             self.stdout.flush()
  36             datasource.sync()
  37             self.stdout.write(datasource.get_status_display())
  38             self.stdout.flush()
  39 
  40         if len(options['name']) > 1:
  41             self.stdout.write(f"Finished.")
  42
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/netbox/core/management/commands/syncdatasource.py b/netbox/core/management/commands/syncdatasource.py
  --- a/netbox/core/management/commands/syncdatasource.py
  +++ b/netbox/core/management/commands/syncdatasource.py
  @@ -1,5 +1,6 @@
   from django.core.management.base import BaseCommand, CommandError
   
  +from core.choices import DataSourceStatusChoices
   from core.models import DataSource
   
   
  @@ -33,9 +34,13 @@
           for i, datasource in enumerate(datasources, start=1):
               self.stdout.write(f"[{i}] Syncing {datasource}... ", ending='')
               self.stdout.flush()
  -            datasource.sync()
  -            self.stdout.write(datasource.get_status_display())
  -            self.stdout.flush()
  +            try:
  +                datasource.sync()
  +                self.stdout.write(datasource.get_status_display())
  +                self.stdout.flush()
  +            except Exception as e:
  +                DataSource.objects.filter(pk=datasource.pk).update(status=DataSourceStatusChoices.FAILED)
  +                raise e
   
           if len(options['name']) > 1:
               self.stdout.write(f"Finished.")
  
verification_info: {"golden_diff": "diff --git a/netbox/core/management/commands/syncdatasource.py b/netbox/core/management/commands/syncdatasource.py\n--- a/netbox/core/management/commands/syncdatasource.py\n+++ b/netbox/core/management/commands/syncdatasource.py\n@@ -1,5 +1,6 @@\n from django.core.management.base import BaseCommand, CommandError\n \n+from core.choices import DataSourceStatusChoices\n from core.models import DataSource\n \n \n@@ -33,9 +34,13 @@\n         for i, datasource in enumerate(datasources, start=1):\n             self.stdout.write(f\"[{i}] Syncing {datasource}... \", ending='')\n             self.stdout.flush()\n-            datasource.sync()\n-            self.stdout.write(datasource.get_status_display())\n-            self.stdout.flush()\n+            try:\n+                datasource.sync()\n+                self.stdout.write(datasource.get_status_display())\n+                self.stdout.flush()\n+            except Exception as e:\n+                DataSource.objects.filter(pk=datasource.pk).update(status=DataSourceStatusChoices.FAILED)\n+                raise e\n \n         if len(options['name']) > 1:\n             self.stdout.write(f\"Finished.\")\n", "issue": "Datasources stuck in sync when using git + ssh from ./manage.py syncdatasource\n### NetBox version\n\nv3.6.1\n\n### Python version\n\n3.11\n\n### Steps to Reproduce\n\nIn Data Sources\r\nAdd\r\nName: test\r\nType: git\r\nURL: git@github.com:netbox-community/netbox.git\r\nCreate\r\n\r\ndocker compose exec netbox ./manage.py syncdatasource test\r\n\r\n\r\n\r\n\n\n### Expected Behavior\n\nUsually leads to some sort of ssh question or failure, and I would expect the exception to set the status to failed, and then be able to hit sync again.\r\n\r\nI'm not sure exactly how NetBox works, but looking at one of the exceptions...\r\ncore.exceptions.SyncError: Fetching remote data failed (HangupException): \r\n\r\nclass SyncError(Exception):\r\n    pass\r\n\r\nDoes this mean the status is not being reset correctly due to the status being left as syncing?\r\n\r\n\n\n### Observed Behavior\n\ndatasource.status = syncing in nbshell\r\n'syncing' in gui\r\nSync option is now greyed out and cannot reset status without manually setting it in nbshell:\r\n\r\nfor d in DataSource.objects.filter(status='syncing'):\r\n  d.status = 'failed'\r\n  d.save()\r\n\n", "before_files": [{"content": "from django.core.management.base import BaseCommand, CommandError\n\nfrom core.models import DataSource\n\n\nclass Command(BaseCommand):\n    help = \"Synchronize a data source from its remote upstream\"\n\n    def add_arguments(self, parser):\n        parser.add_argument('name', nargs='*', help=\"Data source(s) to synchronize\")\n        parser.add_argument(\n            \"--all\", action='store_true', dest='sync_all',\n            help=\"Synchronize all data sources\"\n        )\n\n    def handle(self, *args, **options):\n\n        # Find DataSources to sync\n        if options['sync_all']:\n            datasources = DataSource.objects.all()\n        elif options['name']:\n            datasources = DataSource.objects.filter(name__in=options['name'])\n            # Check for invalid names\n            found_names = {ds['name'] for ds in datasources.values('name')}\n            if invalid_names := set(options['name']) - found_names:\n                raise CommandError(f\"Invalid data source names: {', '.join(invalid_names)}\")\n        else:\n            raise CommandError(f\"Must specify at least one data source, or set --all.\")\n\n        if len(options['name']) > 1:\n            self.stdout.write(f\"Syncing {len(datasources)} data sources.\")\n\n        for i, datasource in enumerate(datasources, start=1):\n            self.stdout.write(f\"[{i}] Syncing {datasource}... \", ending='')\n            self.stdout.flush()\n            datasource.sync()\n            self.stdout.write(datasource.get_status_display())\n            self.stdout.flush()\n\n        if len(options['name']) > 1:\n            self.stdout.write(f\"Finished.\")\n", "path": "netbox/core/management/commands/syncdatasource.py"}], "after_files": [{"content": "from django.core.management.base import BaseCommand, CommandError\n\nfrom core.choices import DataSourceStatusChoices\nfrom core.models import DataSource\n\n\nclass Command(BaseCommand):\n    help = \"Synchronize a data source from its remote upstream\"\n\n    def add_arguments(self, parser):\n        parser.add_argument('name', nargs='*', help=\"Data source(s) to synchronize\")\n        parser.add_argument(\n            \"--all\", action='store_true', dest='sync_all',\n            help=\"Synchronize all data sources\"\n        )\n\n    def handle(self, *args, **options):\n\n        # Find DataSources to sync\n        if options['sync_all']:\n            datasources = DataSource.objects.all()\n        elif options['name']:\n            datasources = DataSource.objects.filter(name__in=options['name'])\n            # Check for invalid names\n            found_names = {ds['name'] for ds in datasources.values('name')}\n            if invalid_names := set(options['name']) - found_names:\n                raise CommandError(f\"Invalid data source names: {', '.join(invalid_names)}\")\n        else:\n            raise CommandError(f\"Must specify at least one data source, or set --all.\")\n\n        if len(options['name']) > 1:\n            self.stdout.write(f\"Syncing {len(datasources)} data sources.\")\n\n        for i, datasource in enumerate(datasources, start=1):\n            self.stdout.write(f\"[{i}] Syncing {datasource}... \", ending='')\n            self.stdout.flush()\n            try:\n                datasource.sync()\n                self.stdout.write(datasource.get_status_display())\n                self.stdout.flush()\n            except Exception as e:\n                DataSource.objects.filter(pk=datasource.pk).update(status=DataSourceStatusChoices.FAILED)\n                raise e\n\n        if len(options['name']) > 1:\n            self.stdout.write(f\"Finished.\")\n", "path": "netbox/core/management/commands/syncdatasource.py"}]}