Skip to content

Commit 6ad05b1

Browse files
committed
Add script that will configure a GitHub checkout for RTD. Fixes #2725.
1 parent ec23bc9 commit 6ad05b1

File tree

1 file changed

+168
-0
lines changed

1 file changed

+168
-0
lines changed

scripts/add-project.py

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"""
2+
Script to add a Git project as cloned from a Github repository
3+
to ReadTheDocs. Invoke from the checkout.
4+
"""
5+
6+
__requires__ = ['requests-toolbelt', 'autocommand', 'keyring']
7+
8+
9+
import os
10+
import getpass
11+
import re
12+
import urllib.parse
13+
import subprocess
14+
import pathlib
15+
16+
import autocommand
17+
import keyring
18+
from requests_toolbelt import sessions
19+
20+
21+
rtd = sessions.BaseUrlSession(
22+
os.environ.get('RTD_URL', 'https://readthedocs.org/api/v2/'))
23+
github = sessions.BaseUrlSession('https://api.github.com/')
24+
github.headers.update(Accept='application/vnd.github.v3+json')
25+
26+
27+
class User:
28+
"""
29+
A User (with a password) in RTD.
30+
31+
Resolves username using ``getpass.getuser()``. Override with
32+
RTD_USERNAME environment variable.
33+
34+
Resolves password using keyring. Install keyring and run
35+
``keyring set https://readthedocs.org/ $USER`` to set the pw.
36+
Override with RTD_PASSWORD environment variable.
37+
"""
38+
def __init__(self):
39+
self.name = os.environ.get('RTD_USERNAME') or getpass.getuser()
40+
system = rtd.create_url('/')
41+
self.password = (
42+
os.environ.get('RTD_PASSWORD')
43+
or keyring.get_password(system, self.name)
44+
)
45+
46+
@property
47+
def id(self):
48+
resp = rtd.get('../v1/user/', params=dict(username=self.name))
49+
resp.raise_for_status()
50+
ob, = resp.json()['objects']
51+
return ob['id']
52+
53+
@property
54+
def tuple(self):
55+
return self.name, self.password
56+
57+
58+
class Sluggable(str):
59+
"""
60+
A name for use in RTD with a 'slug' version.
61+
"""
62+
@property
63+
def slug(self):
64+
return self.replace('.', '')
65+
66+
67+
class Repo:
68+
"""
69+
A Git repo
70+
"""
71+
72+
def __init__(self, root):
73+
self.root = root
74+
cmd = ['git', '-C', root, 'remote', 'get-url', 'origin']
75+
proc = subprocess.run(
76+
cmd, check=True, text=True, stdout=subprocess.PIPE)
77+
self.url = proc.stdout.strip()
78+
79+
@property
80+
def name(self):
81+
return Sluggable(pathlib.Path(self.url).stem)
82+
83+
84+
def create_project(repo):
85+
"""
86+
Create the project with Sluggable name
87+
"""
88+
user = User()
89+
payload = dict(
90+
repo=repo.url,
91+
slug=repo.name.slug,
92+
name=repo.name,
93+
users=[user.id],
94+
)
95+
resp = rtd.post('project/', json=payload, auth=user.tuple)
96+
resp.raise_for_status()
97+
98+
99+
def configure_github(name, url):
100+
"""
101+
Given a project name and webhook URL, configure the webhook
102+
in GitHub.
103+
104+
Resolves username from ``getpass.getuser()``. Override with
105+
``GITHUB_USERNAME``.
106+
107+
Resolves access token from keyring for username and system
108+
'github.com'. Override with ``GITHUB_TOKEN`` environment
109+
variable.
110+
"""
111+
user = os.environ.get('GITHUB_USERNAME') or getpass.getuser()
112+
token = (
113+
keyring.get_password('github.com', user)
114+
or os.environ['GITHUB_TOKEN']
115+
)
116+
headers = dict(Authorization=f'token {token}')
117+
path = f'/repos/{user}/{name}/hooks'
118+
params = dict(
119+
name='web',
120+
config=dict(
121+
url=url,
122+
content_type='json',
123+
),
124+
)
125+
github.post(path, json=params, headers=headers)
126+
127+
128+
def configure_webhook(name):
129+
"""
130+
Identify the webhook URL for a RTD project named name.
131+
"""
132+
login_path = '/accounts/login/'
133+
resp = rtd.get(login_path)
134+
token = rtd.cookies.get('csrftoken') or rtd.cookies['csrf']
135+
user = User()
136+
params = dict(
137+
login=user.name,
138+
password=user.password,
139+
csrfmiddlewaretoken=token,
140+
next='/',
141+
)
142+
headers = dict(Referer=rtd.create_url(login_path))
143+
resp = rtd.post(login_path, data=params, headers=headers)
144+
token = rtd.cookies.get('csrftoken') or rtd.cookies['csrf']
145+
params = dict(
146+
integration_type='github_webhook',
147+
csrfmiddlewaretoken=token,
148+
next='/',
149+
)
150+
create_path = f'/dashboard/{name.slug}/integrations/create/'
151+
headers = dict(
152+
Referer=rtd.create_url(create_path),
153+
)
154+
resp = rtd.post(
155+
create_path,
156+
data=params,
157+
headers=headers,
158+
)
159+
resp.raise_for_status()
160+
ref = re.search(f'<a href="(.*?)">.*?webhook.*?</a>', resp.text).group(1)
161+
return urllib.parse.urljoin(resp.url, ref)
162+
163+
164+
@autocommand.autocommand(__name__)
165+
def main(repo: Repo = Repo('.')):
166+
create_project(repo)
167+
url = configure_webhook(repo.name)
168+
configure_github(repo.name, url)

0 commit comments

Comments
 (0)