Skip to content

Commit 4cd3c48

Browse files
committed
New management command create_group_wikis. Work in progress.
- Legacy-Id: 12149
1 parent ca59bda commit 4cd3c48

2 files changed

Lines changed: 312 additions & 1 deletion

File tree

ietf/settings.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,14 +659,44 @@ def skip_unreadable_post(record):
659659
"left_menu" : "on",
660660
}
661661

662-
TRAC_ADMIN_CMD = "/usr/bin/trac-admin"
662+
TRAC_MASTER_DIR = "/a/www/trac-setup/"
663663
TRAC_WIKI_DIR_ROOT = "/a/www/www6s/trac/"
664664
TRAC_WIKI_DIR_PATTERN = os.path.join(TRAC_WIKI_DIR_ROOT, "%s")
665665
TRAC_WIKI_URL_PATTERN = "https://trac.ietf.org/trac/%s/wiki"
666666
TRAC_ISSUE_URL_PATTERN = "https://trac.ietf.org/trac/%s/report/1"
667667
TRAC_SVN_DIR_PATTERN = "/a/svn/group/%s"
668668
TRAC_SVN_URL_PATTERN = "https://svn.ietf.org/svn/group/%s/"
669669

670+
TRAC_ENV_OPTIONS = [
671+
('project', 'name', "{name} Wiki"),
672+
('trac', 'database', 'sqlite:db/trac.db' ),
673+
('trac', 'repository_type', 'svn'),
674+
('trac', 'repository_dir', "{svn_dir}"),
675+
('inherit', 'file', "/a/www/trac-setup/conf/trac.ini"),
676+
]
677+
678+
TRAC_WIKI_PAGES_TEMPLATES = [
679+
"utils/wiki/IetfSpecificFeatures",
680+
"utils/wiki/InterMapTxt",
681+
"utils/wiki/SvnTracHooks",
682+
"utils/wiki/ThisTracInstallation",
683+
"utils/wiki/TrainingMaterials",
684+
"utils/wiki/WikiStart",
685+
]
686+
687+
TRAC_ISSUE_SEVERITY_ADD = [
688+
"-",
689+
"Candidate WG Document",
690+
"Active WG Document",
691+
"Waiting for Expert Review",
692+
"In WG Last Call",
693+
"Waiting for Shepherd Writeup",
694+
"Submitted WG Document",
695+
"Dead WG Document",
696+
]
697+
698+
SVN_ADMIN_COMMAND = "/usr/bin/svnadmin"
699+
670700
# Email addresses people attempt to set for their account will be checked
671701
# against the following list of regex expressions with re.search(pat, addr):
672702
EXLUDED_PERSONAL_EMAIL_REGEX_PATTERNS = ["@ietf.org$"]
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
# Copyright 2016 IETF Trust
2+
3+
import os
4+
import copy
5+
import syslog
6+
import pkg_resources
7+
#from optparse import make_option
8+
9+
from trac.admin.api import AdminCommandManager
10+
from trac.core import TracError
11+
from trac.env import Environment
12+
from trac.perm import PermissionSystem
13+
from trac.ticket.model import Component, Milestone, Severity
14+
from trac.util.text import unicode_unquote
15+
from trac.versioncontrol.api import RepositoryManager
16+
from trac.wiki.admin import WikiAdmin
17+
from trac.wiki.model import WikiPage
18+
19+
from django.conf import settings
20+
from django.core.management.base import BaseCommand, CommandError
21+
from django.template.loader import render_to_string
22+
23+
import debug # pyflakes:ignore
24+
25+
from ietf.group.models import Group, GroupURL
26+
from ietf.utils.pipe import pipe
27+
28+
logtag = __name__.split('.')[-1]
29+
logname = "user.log"
30+
syslog.openlog(logname, syslog.LOG_PID, syslog.LOG_USER)
31+
32+
class Command(BaseCommand):
33+
help = "Create group wikis for WGs, RGs and Areas which don't have one."
34+
35+
option_list = BaseCommand.option_list + (
36+
)
37+
#verbosity = 1
38+
39+
def note(self, msg):
40+
if self.verbosity > 1:
41+
self.stdout.write(msg)
42+
43+
def log(self, msg):
44+
syslog.syslog(msg)
45+
self.stderr.write(msg)
46+
47+
# --- svn ---
48+
49+
def do_cmd(self, cmd, *args):
50+
quoted_args = [ '"%s"'%a if ' ' in a else a for a in args ]
51+
self.note("Running %s %s ..." % (os.path.basename(cmd), " ".join(quoted_args)))
52+
command = [ cmd, ] + list(args)
53+
code, out, err = pipe(command)
54+
msg = None
55+
if code != 0:
56+
msg = "Error %s: %s when executing '%s'" % (code, err, " ".join(command))
57+
self.log(msg)
58+
return msg, out
59+
60+
def svn_admin_cmd(self, *args):
61+
return self.do_cmd(settings.SVN_ADMIN_COMMAND, *args)
62+
63+
def create_svn(self, svn):
64+
self.note(" Creating svn repository: %s" % svn)
65+
if not os.path.exists(os.path.dirname(svn)):
66+
msg = "Intended to create '%s', but parent directory is missing" % svn
67+
self.log(msg)
68+
return msg
69+
err, out= self.svn_admin_cmd("create", svn )
70+
return err
71+
72+
# --- trac ---
73+
74+
def remove_demo_components(self, group, env):
75+
for component in Component.select(env):
76+
if component.name.startswith('component'):
77+
component.delete()
78+
79+
def remove_demo_milestones(self, group, env):
80+
for milestone in Milestone.select(env):
81+
if milestone.name.startswith('milestone'):
82+
milestone.delete()
83+
84+
def symlink_to_master_assets(self, group, env):
85+
master_dir = settings.TRAC_MASTER_DIR
86+
master_htdocs = os.path.join(master_dir, "htdocs")
87+
group_htdocs = os.path.join(group.trac_dir, "htdocs")
88+
self.note(" Symlinking %s to %s" % (master_htdocs, group_htdocs))
89+
os.removedirs(group_htdocs)
90+
os.symlink(master_htdocs, group_htdocs)
91+
92+
def add_wg_draft_states(self, group, env):
93+
for state in settings.TRAC_ISSUE_SEVERITY_ADD:
94+
self.note(" Adding severity %s" % state)
95+
severity = Severity(env)
96+
severity.name = state
97+
severity.insert()
98+
99+
def add_wiki_page(self, env, name, text):
100+
page = WikiPage(env, name)
101+
if page.time:
102+
self.note(" ** Page %s already exists, not adding it." % name)
103+
return
104+
page.text = text
105+
page.save(author="(System)", comment="Initial page import")
106+
107+
def add_default_wiki_pages(self, group, env):
108+
dir = pkg_resources.resource_filename('trac.wiki', 'default-pages')
109+
#WikiAdmin(env).load_pages(dir)
110+
with env.db_transaction:
111+
for name in os.listdir(dir):
112+
filename = os.path.join(dir, name)
113+
name = unicode_unquote(name.encode('utf-8'))
114+
if os.path.isfile(filename):
115+
self.note(" Adding page %s" % name)
116+
with open(filename) as file:
117+
text = file.read().decode('utf-8')
118+
self.add_wiki_page(env, name, text)
119+
120+
def add_custom_wiki_pages(self, group, env):
121+
for templ in settings.TRAC_WIKI_PAGES_TEMPLATES:
122+
_, name = os.path.split(templ)
123+
text = render_to_string(templ, {"group": group})
124+
self.note(" Adding page %s" % name)
125+
126+
def sync_default_repository(self, group, env):
127+
repository = env.get_repository('')
128+
if repository:
129+
self.note(" Indexing default repository")
130+
repository.sync()
131+
132+
def create_trac(self, group):
133+
if not os.path.exists(os.path.dirname(group.trac_dir)):
134+
msg = "Intended to create '%s', but parent directory is missing" % group.trac_dir
135+
self.log(msg)
136+
return None
137+
options = copy.deepcopy(settings.TRAC_ENV_OPTIONS)
138+
# Interpolate group field names to values in the option settings:
139+
for i in range(len(options)):
140+
sect, key, val = options[i]
141+
val = val.format(**group.__dict__)
142+
options[i] = sect, key, val
143+
# Try to creat ethe environment, remove unwanted defaults, and add
144+
# custom pages and settings.
145+
try:
146+
debug.show('options')
147+
env = Environment(group.trac_dir, create=True, options=options)
148+
self.remove_demo_components(group, env)
149+
self.remove_demo_milestones(group, env)
150+
self.maybe_add_group_url(group, 'Wiki', settings.TRAC_WIKI_URL_PATTERN % group.acronym)
151+
self.maybe_add_group_url(group, 'Issue tracker', settings.TRAC_ISSUE_URL_PATTERN % group.acronym)
152+
# Use custom assets (if any) from the master setup
153+
self.symlink_to_master_assets(group, env)
154+
if group.type_id == 'wg':
155+
self.add_wg_draft_states(group, env)
156+
self.add_default_wiki_pages(group, env)
157+
self.add_custom_wiki_pages(group, env)
158+
self.sync_default_repository(group, env)
159+
# Components (i.e., drafts) will be handled during components
160+
# update later
161+
# Permissions will be handled during permission update later.
162+
return env
163+
except TracError as e:
164+
self.log("While creating trac instance for %s: %s" % (group, e))
165+
raise
166+
return None
167+
168+
def update_trac_permissions(self, group, env):
169+
mgr = PermissionSystem(env)
170+
permission_list = mgr.get_all_permissions()
171+
permission_list = [ (u,a) for (u,a) in permission_list if not u in ['anonymous', 'authenticated']]
172+
permissions = {}
173+
for user, action in permission_list:
174+
if not user in permissions:
175+
permissions[user] = []
176+
permissions[user].append(action)
177+
roles = group.role_set.filter(name_id__in=['chair', 'secr', 'ad'])
178+
users = []
179+
for role in roles:
180+
user = role.email.address.lower()
181+
users.append(user)
182+
if not user in permissions:
183+
try:
184+
mgr.grant_permission(user, 'TRAC_ADMIN')
185+
self.note(" Granting admin permission for %s" % user)
186+
except TracError as e:
187+
self.log("While adding admin permission for %s: %s" (user, e))
188+
for user in permissions:
189+
if not user in users:
190+
if 'TRAC_ADMIN' in permissions[user]:
191+
try:
192+
self.note(" Revoking admin permission for %s" % user)
193+
mgr.revoke_permission(user, 'TRAC_ADMIN')
194+
except TracError as e:
195+
self.log("While revoking admin permission for %s: %s" (user, e))
196+
197+
def update_trac_components(self, group, env):
198+
components = Component.select(env)
199+
comp_names = [ c.name for c in components ]
200+
group_docs = group.document_set.filter(states__slug='active', type_id='draft').distinct()
201+
group_comp = []
202+
for doc in group_docs:
203+
if not doc.name.startswith('draft-'):
204+
self.log("While adding components: unexpectd %s group doc name: %s" % (group.acronym, doc.name))
205+
continue
206+
name = doc.name[len('draft-'):]
207+
if name.startswith('ietf-'):
208+
name = name[len('ietf-'):]
209+
elif name.startswith('irtf-'):
210+
name = name[len('ietf-'):]
211+
if name.startswith(group.acronym+'-'):
212+
name = name[len(group.acronym+'-'):]
213+
group_comp.append(name)
214+
if not name in comp_names and not doc.name in comp_names:
215+
self.note(" Group draft: %s" % doc.name)
216+
self.note(" Adding component %s" % name)
217+
comp = Component(env)
218+
comp.name = name
219+
comp.owner = "%s@ietf.org" % doc.name
220+
comp.insert()
221+
222+
def maybe_add_group_url(self, group, name, url):
223+
urls = [ u for u in group.groupurl_set.all() if name.lower() in u.name.lower() ]
224+
if not urls:
225+
self.note(" adding %s %s URL ..." % (group.acronym, name.lower()))
226+
group.groupurl_set.add(GroupURL(group=group, name=name, url=url))
227+
228+
def add_custom_pages(self, group, env):
229+
for template_name in settings.TRAC_WIKI_PAGES_TEMPLATES:
230+
pass
231+
232+
def add_custom_group_states(self, group, env):
233+
for state_name in settings.TRAC_ISSUE_SEVERITY_ADD:
234+
pass
235+
236+
# --------------------------------------------------------------------
237+
238+
def handle(self, *filenames, **options):
239+
self.verbosity = options['verbosity']
240+
self.errors = 0
241+
242+
if self.verbosity.isdigit():
243+
self.verbosity = int(self.verbosity)
244+
245+
if not os.path.exists(settings.TRAC_WIKI_DIR_ROOT):
246+
raise CommandError('The Wiki base direcory specified in settings.TRAC_WIKI_DIR_ROOT (%s) does not exist.' % settings.TRAC_WIKI_DIR_ROOT)
247+
248+
groups = Group.objects.filter(
249+
type__slug__in=['wg','rg','area'],
250+
state__slug='active'
251+
)
252+
253+
for group in groups:
254+
try:
255+
self.note("Processing group %s" % group.acronym)
256+
group.trac_dir = settings.TRAC_WIKI_DIR_PATTERN % group.acronym
257+
group.svn_dir = settings.TRAC_SVN_DIR_PATTERN % group.acronym
258+
259+
if not os.path.exists(group.svn_dir):
260+
err = self.create_svn(group.svn_dir)
261+
self.errors += 1 if err else 0
262+
263+
if not os.path.exists(group.trac_dir):
264+
trac_env = self.create_trac(group)
265+
self.errors += 1 if not trac_env else 0
266+
else:
267+
trac_env = Environment(group.trac_dir)
268+
269+
if not trac_env:
270+
continue
271+
272+
self.update_trac_permissions(group, trac_env)
273+
self.update_trac_components(group, trac_env)
274+
275+
except Exception as e:
276+
self.errors += 1
277+
self.log("While processing %s: %s" % (group.acronym, e))
278+
raise
279+
280+
if self.errors:
281+
raise CommandError("There were %s failures in WG Trac creation, see syslog %s for details." % (self.errors, logname))

0 commit comments

Comments
 (0)