0f75cd40b4ce537335e3302a72b944ec2cabaecf
[mdk.git] / mdk / commands / doctor.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 Moodle Development Kit
6
7 Copyright (c) 2013 Frédéric Massart - FMCorz.net
8
9 This program is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22 http://github.com/FMCorz/mdk
23 """
24
25 import os
26 import re
27 import shutil
28 import subprocess
29 from .. import git
30 from ..command import Command
31 from ..tools import mkdir, resolveEditor
32
33
34 class DoctorCommand(Command):
35
36 _arguments = [
37 (
38 ['--fix'],
39 {
40 'action': 'store_true',
41 'help': 'Automatically fix all the identified problems'
42 }
43 ),
44 (
45 ['--all'],
46 {
47 'action': 'store_true',
48 'help': 'Enable all the checks'
49 }
50 ),
51 (
52 ['--branch'],
53 {
54 'action': 'store_true',
55 'help': 'Check the branch checked out on your integration instances'
56 }
57 ),
58 (
59 ['--cached'],
60 {
61 'action': 'store_true',
62 'help': 'Check the cached repositories'
63 }
64 ),
65 (
66 ['--dependencies'],
67 {
68 'action': 'store_true',
69 'help': 'Check various dependencies'
70 }
71 ),
72 (
73 ['--directories'],
74 {
75 'action': 'store_true',
76 'help': 'Check the directories set in the config file'
77 }
78 ),
79 (
80 ['--hi'],
81 {
82 'action': 'store_true',
83 'help': 'What you see it totally unrelated to what you get',
84 'silent': True
85 }
86 ),
87 (
88 ['--masterbranch'],
89 {
90 'action': 'store_true',
91 'help': 'Check the status of the master branch'
92 }
93 ),
94 (
95 ['--remotes'],
96 {
97 'action': 'store_true',
98 'help': 'Check the remotes of your instances'
99 }
100 ),
101 (
102 ['--symlink'],
103 {
104 'action': 'store_true',
105 'help': 'Check the symlinks of the instances'
106 }
107 ),
108 (
109 ['--wwwroot'],
110 {
111 'action': 'store_true',
112 'help': 'Check the $CFG->wwwroot of your instances'
113 }
114 )
115 ]
116 _description = 'Perform several checks on your current installation'
117
118 def run(self, args):
119
120 optionsCount = sum([1 for k, v in vars(args).items() if v != False])
121 if optionsCount == 0 or (optionsCount == 1 and args.fix):
122 self.argumentError('You should probably tell me what symptoms you are experiencing')
123
124 allChecks = False
125 if args.all:
126 allChecks = True
127
128 # Check directories
129 if args.directories or allChecks:
130 self.directories(args)
131
132 # Check the cached remotes
133 if args.cached or allChecks:
134 self.cachedRepositories(args)
135
136 # Check the dependencies
137 if args.dependencies or allChecks:
138 self.dependencies(args)
139
140 # Check instances remotes
141 if args.remotes or allChecks:
142 self.remotes(args)
143
144 # Check instances wwwroot
145 if args.wwwroot or allChecks:
146 self.wwwroot(args)
147
148 # Check instances symlink
149 if args.symlink or allChecks:
150 self.symlink(args)
151
152 # Check the branches
153 if args.branch or allChecks:
154 self.branch(args)
155
156 # Check the master branch
157 if args.masterbranch or allChecks:
158 self.masterbranch(args)
159
160 # Check what you see is what you get
161 if args.hi:
162 self.hi(args)
163
164 def branch(self, args):
165 """Make sure the correct branch is checked out. Only on integration branches."""
166
167 print 'Checking integration instances branches'
168
169 if not self._checkWorkplace():
170 return
171
172 instances = self.Wp.list(integration=True)
173
174 for identifier in instances:
175 M = self.Wp.get(identifier)
176 stablebranch = M.get('stablebranch')
177 currentbranch = M.currentBranch()
178 if stablebranch != currentbranch:
179 print ' %s is on branch %s instead of %s' % (identifier, currentbranch, stablebranch)
180 if args.fix:
181 print ' Checking out %s' % (stablebranch)
182 if not M.git().checkout(stablebranch):
183 print ' Error: Checkout unsucessful!'
184
185 def cachedRepositories(self, args):
186 """Ensure that the cached repositories are valid"""
187
188 print 'Checking cached repositories'
189 cache = os.path.abspath(os.path.realpath(os.path.expanduser(self.C.get('dirs.mdk'))))
190
191 dirs = [
192 {
193 'dir': os.path.join(cache, 'moodle.git'),
194 'url': self.C.get('remotes.stable')
195 },
196 {
197 'dir': os.path.join(cache, 'integration.git'),
198 'url': self.C.get('remotes.integration')
199 },
200 ]
201
202 for d in dirs:
203 directory = d['dir']
204 name = os.path.split(directory)[1]
205
206 if os.path.isdir(directory):
207 if os.path.isdir(os.path.join(directory, '.git')):
208 print ' %s is not a bare repository' % name
209 if args.fix:
210 print ' Renaming %s/.git directory to %s' % (directory, directory)
211 os.rename(directory, directory + '.tmp')
212 os.rename(os.path.join(directory + '.tmp', '.git'), directory)
213 shutil.rmtree(directory + '.tmp')
214
215 repo = git.Git(directory, self.C.get('git'))
216 if repo.getConfig('core.bare') != 'true':
217 print ' %s core.bare is not set to true' % name
218 if args.fix:
219 print ' Setting core.bare to true'
220 repo.setConfig('core.bare', 'true')
221
222 if repo.getConfig('remote.origin.url') != d['url']:
223 print ' %s uses an different origin (%s)' % (name, repo.getConfig('remote.origin.url'))
224 if args.fix:
225 print ' Setting remote.origin.url to %s' % d['url']
226 repo.setConfig('remote.origin.url', d['url'])
227
228 if repo.getConfig('remote.origin.fetch') != '+refs/*:refs/*':
229 print ' %s fetch value is invalid (%s)' % (name, repo.getConfig('remote.origin.fetch'))
230 if args.fix:
231 print ' Setting remote.origin.fetch to %s' % '+refs/*:refs/*'
232 repo.setConfig('remote.origin.fetch', '+refs/*:refs/*')
233
234 def dependencies(self, args):
235 """Check that various dependencies are met"""
236
237 print 'Checking dependencies'
238
239 # Check binaries.
240 hasErrors = False
241 for k in ['git', 'php', 'java', 'recess', 'lessc', 'shifter', 'yuidoc']:
242 path = self.C.get(k)
243 if not path or not os.path.isfile(path):
244 print ' The path to \'%s\' is invalid: %s' % (k, path)
245 hasErrors = True
246 if hasErrors and args.fix:
247 print ' Please manually fix the paths in your config file'
248
249 # Checking editor.
250 editor = resolveEditor()
251 if editor:
252 try:
253 # Check if it is callable.
254 proc = subprocess.Popen(editor, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
255 proc.kill()
256 except OSError:
257 editor = None
258
259 if not editor:
260 print ' Could not resolve the path to your editor'
261 if args.fix:
262 print ' Set $EDITOR, /usr/bin/editor, or use: mdk config set editor [path]'
263
264 def directories(self, args):
265 """Check that the directories are valid"""
266
267 print 'Checking directories'
268 for k, d in self.C.get('dirs').items():
269 d = os.path.abspath(os.path.realpath(os.path.expanduser(d)))
270 if not os.path.isdir(d):
271 print ' %s does not exist' % d
272 if args.fix:
273 print ' Creating %s' % d
274 mkdir(d, 0777)
275
276 if not self._checkWorkplace():
277 return
278
279 # Checking extra directory.
280 instances = self.Wp.list()
281 for identifier in instances:
282 d = self.Wp.getPath(identifier, 'extra')
283 if not os.path.isdir(d):
284 print ' %s does not exist' % d
285 if args.fix:
286 print ' Creating %s' % d
287 mkdir(d, 0777)
288
289 def masterbranch(self, args):
290 """Checks the current master branch and the value set in config."""
291
292 print 'Checking master branch'
293
294 if not self._checkWorkplace():
295 return
296
297 repoPath = self.Wp.getCachedRemote()
298 if not os.path.isdir(repoPath):
299 return
300
301 try:
302 self.Wp.updateCachedClones(verbose=False)
303 except Exception:
304 print ' Could not update clone, please try again.'
305 return
306
307 repo = git.Git(repoPath, self.C.get('git'))
308 result = repo.execute(['show', 'master:version.php'])
309 if result[0] != 0:
310 print ' Could not read the master version.php'
311 return
312
313 reBranch = re.compile(r'^\s*\$branch\s*=\s*(?P<brackets>[\'"])?([0-9]+)(?P=brackets)\s*;')
314 latestBranch = None
315 for line in result[1].split('\n'):
316 if reBranch.search(line):
317 latestBranch = int(reBranch.search(line).group(2))
318
319 masterBranch = int(self.C.get('masterBranch'))
320 if not latestBranch:
321 print ' Oops, could not identify the mater branch'
322 elif masterBranch != latestBranch:
323 print ' The config masterBranch is set to %d, expecting %d' % (masterBranch, latestBranch)
324 if args.fix:
325 print ' Setting masterBranch to %d' % (latestBranch)
326 self.C.set('masterBranch', latestBranch)
327
328
329 def hi(self, args):
330 """I wonder what is the purpose of this...
331
332 hint #1: 1341
333 hint #2: dobedobedoh
334 """
335
336 if args.fix:
337 print 'The horse is a noble animal'
338 else:
339 print '<em>Hi</em>'
340
341 def symlink(self, args):
342 """Check that the symlinks exist"""
343
344 print 'Checking symlinks'
345
346 if not self._checkWorkplace():
347 return
348
349 instances = self.Wp.list()
350 for identifier in instances:
351
352 wwwLink = os.path.join(self.Wp.www, identifier)
353 if not os.path.exists(wwwLink):
354 print ' Missing link to www for %s' % (identifier)
355 if args.fix:
356 print ' Creating www symlink for %s' % (identifier)
357 os.symlink(self.Wp.getPath(identifier, 'www'), wwwLink)
358
359 extraLink = os.path.join(self.Wp.getMdkWebDir(), identifier)
360 if not os.path.exists(extraLink):
361 print ' Missing link to extra for %s' % (identifier)
362 if args.fix:
363 print ' Creating extra symlink for %s' % (identifier)
364 os.symlink(self.Wp.getPath(identifier, 'extra'), extraLink)
365
366
367 def remotes(self, args):
368 """Check that the correct remotes are used"""
369
370 print 'Checking remotes'
371
372 if not self._checkWorkplace():
373 return
374
375 remotes = {
376 'mine': self.C.get('remotes.mine'),
377 'stable': self.Wp.getCachedRemote() if self.C.get('useCacheAsUpstreamRemote') else self.C.get('remotes.stable'),
378 'integration': self.Wp.getCachedRemote(True) if self.C.get('useCacheAsUpstreamRemote') else self.C.get('remotes.integration')
379 }
380 myRemote = self.C.get('myRemote')
381 upstreamRemote = self.C.get('upstreamRemote')
382
383 instances = self.Wp.list()
384 for identifier in instances:
385 M = self.Wp.get(identifier)
386
387 remote = M.git().getRemote(myRemote)
388 if remote != remotes['mine']:
389 print ' %s: Remote %s is %s, not %s' % (identifier, myRemote, remote, remotes['mine'])
390 if (args.fix):
391 print ' Setting %s to %s' % (myRemote, remotes['mine'])
392 M.git().setRemote(myRemote, remotes['mine'])
393
394 expected = remotes['stable'] if M.isStable() else remotes['integration']
395 remote = M.git().getRemote(upstreamRemote)
396 if remote != expected:
397 print ' %s: Remote %s is %s, not %s' % (identifier, upstreamRemote, remote, expected)
398 if (args.fix):
399 print ' Setting %s to %s' % (upstreamRemote, expected)
400 M.git().setRemote(upstreamRemote, expected)
401
402 def wwwroot(self, args):
403 """Check the wwwroot of the instances"""
404
405 print 'Checking wwwroot'
406
407 if not self._checkWorkplace():
408 return
409
410 instances = self.Wp.resolveMultiple(self.Wp.list())
411
412
413 for M in instances:
414 if not M.isInstalled():
415 continue
416 else:
417 actual = M.get('wwwroot')
418 expected = self.Wp.getUrl(M.get('identifier'))
419 if actual != expected:
420 print ' %s: Found %s, not %s' % (M.get('identifier'), actual, expected)
421 if args.fix:
422 print ' Setting %s on %s' % (expected, M.get('identifier'))
423 M.updateConfig('wwwroot', expected)
424
425 def _checkWorkplace(self, indent=2):
426 """Returns whether the workplace is available or, and print a message if it is not."""
427 try:
428 self.Wp
429 except ImportError:
430 print ' ' * indent + 'The workplace could not be loaded, did you install the dependencies?'
431 return False
432 return True