Skip to content

Commit 1699307

Browse files
authored
Merge pull request #7176 from readthedocs/humitos/script-connect-remoterepository
2 parents 364ef85 + 1ce9364 commit 1699307

File tree

3 files changed

+112
-0
lines changed

3 files changed

+112
-0
lines changed

readthedocs/oauth/management/__init__.py

Whitespace-only changes.

readthedocs/oauth/management/commands/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import json
2+
3+
from django.db.models import Q, Subquery
4+
from django.core.management.base import BaseCommand
5+
6+
from readthedocs.oauth.models import RemoteRepository
7+
from readthedocs.oauth.services import registry
8+
from readthedocs.oauth.services.base import SyncServiceError
9+
from readthedocs.projects.models import Project
10+
from readthedocs.organizations.models import Organization
11+
12+
13+
class Command(BaseCommand):
14+
help = "Re-connect RemoteRepository to Project"
15+
16+
def add_arguments(self, parser):
17+
parser.add_argument('organization', nargs='+', type=str)
18+
parser.add_argument(
19+
'--no-dry-run',
20+
action='store_true',
21+
default=False,
22+
help='Update database with the changes proposed.',
23+
)
24+
25+
# If owners does not have their RemoteRepository synced, it could
26+
# happen we don't find a matching Project (see --force-owners-social-resync)
27+
parser.add_argument(
28+
'--only-owners',
29+
action='store_true',
30+
default=False,
31+
help='Connect repositories only to organization owners.',
32+
)
33+
34+
parser.add_argument(
35+
'--force-owners-social-resync',
36+
action='store_true',
37+
default=False,
38+
help='Force to re-sync RemoteRepository for organization owners.',
39+
)
40+
41+
def _force_owners_social_resync(self, organization):
42+
for owner in organization.owners.all():
43+
for service_cls in registry:
44+
for service in service_cls.for_user(owner):
45+
try:
46+
service.sync()
47+
except SyncServiceError:
48+
print(f'Service {service} failed while syncing. Skipping...')
49+
50+
def _connect_repositories(self, organization, no_dry_run, only_owners):
51+
connected_projects = []
52+
# TODO: consider using same login than RemoteRepository.matches method
53+
# https://github.com/readthedocs/readthedocs.org/blob/49b03f298b6105d755554f7dc7e97a3398f7066f/readthedocs/oauth/models.py#L185-L194
54+
remote_query = (
55+
Q(ssh_url__in=Subquery(organization.projects.values('repo'))) |
56+
Q(clone_url__in=Subquery(organization.projects.values('repo')))
57+
)
58+
for remote in RemoteRepository.objects.filter(remote_query).order_by('pub_date'):
59+
admin = json.loads(remote.json).get('permissions', {}).get('admin')
60+
61+
if only_owners and remote.users.first() not in organization.owners.all():
62+
# Do not connect a RemoteRepository if the User is not owner of the organization
63+
continue
64+
65+
if not admin:
66+
# Do not connect a RemoteRepository where the User is not admin of the repository
67+
continue
68+
69+
if not organization.users.filter(username=remote.users.first().username).exists():
70+
# Do not connect a RemoteRepository if the use does not belong to the organization
71+
continue
72+
73+
# Projects matching
74+
# - RemoteRepository URL
75+
# - are under the Organization
76+
# - not connected to a RemoteRepository already
77+
# - was not connected previously by this call to the script
78+
projects = Project.objects.filter(
79+
Q(repo=remote.ssh_url) | Q(repo=remote.clone_url),
80+
organizations__in=[organization.pk],
81+
remote_repository__isnull=True
82+
).exclude(slug__in=connected_projects)
83+
84+
for project in projects:
85+
connected_projects.append(project.slug)
86+
if no_dry_run:
87+
remote.project = project
88+
remote.save()
89+
90+
print(f'{project.slug: <40} {remote.pk: <10} {remote.html_url: <60} {remote.users.first().username: <20} {admin: <5}') # noqa
91+
print('Total:', len(connected_projects))
92+
if not no_dry_run:
93+
print(
94+
'Changes WERE NOT applied to the database. '
95+
'Run it with --no-dry-run to save the changes.'
96+
)
97+
98+
def handle(self, *args, **options):
99+
no_dry_run = options.get('no_dry_run')
100+
only_owners = options.get('only_owners')
101+
force_owners_social_resync = options.get('force_owners_social_resync')
102+
103+
for organization in options.get('organization'):
104+
try:
105+
organization = Organization.objects.get(slug=organization)
106+
107+
if force_owners_social_resync:
108+
self._force_owners_social_resync(organization)
109+
110+
self._connect_repositories(organization, no_dry_run, only_owners)
111+
except Organization.DoesNotExist:
112+
print(f'Organization does not exist. organization={organization}')

0 commit comments

Comments
 (0)