Source

Webware / bin / MakeAppWorkDir.py

Full commit
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
#!/usr/bin/env python

"""MakeAppWorkDir.py

INTRODUCTION

This utility builds a directory tree that can be used as the current
working directory of an instance of the WebKit application server.
By using a separate directory tree like this, your application can run
without needing write access etc. to the Webware directory tree, and
you can also run more than one application server at once using the
same Webware code. This makes it easy to reuse and keep Webware
updated without disturbing your applications.

USAGE

MakeAppWorkDir.py [Options] WorkDir

Options:
  -c, --context-name=...  The name for the preinstalled context.
                          By default, it will be "MyContext".
  -d, --context-dir=...   The directory where the context will be located,
                          so you can place it outside of the WorkDir.
  -l, --library=...       Other dirs to be included in the search path.
                          You may specify this option multiple times.
  -i, --cvsignore         This will add .cvsignore files to the WorkDir.
  -u, --user=...          The name or uid of the user to own the WorkDir.
                          This option is supported under Unix only.
  -g, --group=...         The name or gid of the group to own the WorkDir.
                          This option is supported under Unix only.

WorkDir:
  The target working directory to be created.
"""

# FUTURE
# * Add options to immediately import the new directory tree into a
#   CVS or SVN repository. In the case of a CVS repository, the
#   .cvsignore files should be created automatically, and in case
#   of SVN, the corresponding svn propset commands should be issued.
# * MakeAppWorkDir.py should set the admin password like install.py does.
#   At the same time, install.py should be able to do a "app-less" install,
#   from which the admin can create appdirs using MakeAppWorkDir.py.
#   The idea here is that the results of install.py should only be "shared"
#   resources, and "per-app" resources (like the admin password) should be
#   deferred (or, at least, deferrable) to MakeAppWorkDir.py.
# CREDITS
# * Contributed to Webware for Python by Robin Dunn
# * Improved by Christoph Zwerschke

import sys, os, stat, re, glob, shutil


class MakeAppWorkDir(object):
    """Make a new application runtime directory for Webware.

    This class breaks down the steps needed to create a new runtime
    directory for Webware. That includes all the needed
    subdirectories, default configuration files, and startup scripts.
    Each step can be overridden in a derived class if needed.

    Existing files will not be overwritten, but access permissions
    will be changed accordingly in any case.

    """

    def __init__(self, webwareDir, workDir, verbose=True, osType=None,
            contextName='MyContext', contextDir='', libraryDirs=None,
            cvsIgnore=False, uid=None, gid=None):
        """Initializer for MakeAppWorkDir.

        Pass in at least the Webware directory and the target working
        directory. If you pass None for contextName then the default
        context will be the the WebKit/Examples directory as usual.

        """
        self._webwareDir = webwareDir
        self._webKitDir = os.path.join(webwareDir, 'WebKit')
        self._workDir = os.path.abspath(workDir)
        self._verbose = verbose
        if osType is None:
            osType = os.name
        self._contextName = contextName
        self._contextDir = contextDir
        if libraryDirs is None:
            libraryDirs = []
        self._libraryDirs = libraryDirs
        self._osType = osType
        self._cvsIgnore = cvsIgnore
        self._uid = uid
        self._gid = gid

    def buildWorkDir(self):
        """These are all the steps needed to make a new runtime directory.

        You can override the steps taken here with your own methods.

        """
        if os.path.exists(self._workDir):
            self.msg("The target directory already exists.")
            self.msg("Adding everything needed for a WebKit runtime directory...")
        else:
            self.msg("Making a new WebKit runtime directory...")
        self.msg()
        self.makeDirectories()
        self.copyConfigFiles()
        self.copyOtherFiles()
        self.makeLauncherScripts()
        if self._contextName is not None:
            self.makeDefaultContext()
        if self._cvsIgnore:
            self.addCvsIgnore()
        self.changeOwner()
        self.printCompleted()

    def makeDirectories(self):
        """Create all the needed directories if they don't already exist."""
        self.msg("Creating the directory tree...")
        standardDirs = (
            '', 'Cache', 'Configs', 'ErrorMsgs', 'Logs', 'Sessions')
        for dir in standardDirs:
            dir = os.path.join(self._workDir, dir)
            if os.path.exists(dir):
                self.msg("\t%s already exists." % dir)
            else:
                os.mkdir(dir)
                self.msg("\t%s" % dir)
        for dir in self._libraryDirs:
            dir = os.path.join(self._workDir, dir)
            if os.path.exists(dir):
                self.msg("\t%s already exists." % dir)
            else:
                os.makedirs(dir)
                open(os.path.join(dir, '__init__.py'), 'w').write('#\n')
                self.msg("\t%s created." % dir)
        self.msg()

    def copyConfigFiles(self):
        """Make a copy of the config files in the Configs directory."""
        self.msg("Copying config files...")
        configs = glob.glob(os.path.join(self._webKitDir,
            'Configs', '*.config'))
        for name in configs:
            newname = os.path.join(self._workDir, "Configs",
                os.path.basename(name))
            if os.path.exists(newname):
                self.msg("\t%s already exists." % newname)
            else:
                self.msg("\t%s" % newname)
                shutil.copyfile(name, newname)
            mode = os.stat(newname)[stat.ST_MODE]
            # remove public read/write/exec perms
            os.chmod(newname, mode & 0770)
        self.msg()

    def copyOtherFiles(self):
        """Make a copy of any other necessary files in the new work dir."""
        self.msg("Copying other files...")
        otherFiles = ('AppServer', 'webkit', 'error404.html')
        for name in otherFiles:
            if name == 'AppServer':
                if self._osType == 'nt':
                    name += '.bat'
                chmod = True
            elif name == 'webkit':
                if self._osType != 'posix':
                    continue
                chmod = True
            else:
                chmod = False
            newname = os.path.join(self._workDir, os.path.basename(name))
            if os.path.exists(newname):
                self.msg("\t%s already exists." % newname)
                if chmod:
                    os.chmod(newname, 0755)
            else:
                oldname = os.path.join(self._webKitDir, name)
                if os.path.exists(oldname):
                    self.msg("\t%s" % newname)
                    shutil.copyfile(oldname, newname)
                    if chmod:
                        os.chmod(newname, 0755)
                else:
                    self.msg("\tWarning: Cannot find %r." % oldname)
        self.msg()

    def makeLauncherScripts(self):
        """Create the launcher scripts and copy the CGI adapter script."""
        self.msg("Creating the launcher scripts...")
        workDir = self._workDir
        webwareDir = self._webwareDir
        webKitDir = self._webKitDir
        libraryDirs = self._libraryDirs
        uid, gid = self._uid, self._gid
        if uid is None:
            user = None
        else:
            import pwd
            user = pwd.getpwuid(uid)[0]
        if gid is None:
            group = None
        else:
            import grp
            group = grp.getgrgid(gid)[0]
        executable = sys.executable
        for name in launcherScripts:
            if name.endswith('Service.py') and self._osType != 'nt':
                continue
            newname = os.path.join(workDir, name)
            if os.path.exists(newname):
                self.msg("\t%s already exists." % newname)
                os.chmod(newname, 0755)
            else:
                oldname = os.path.join(webKitDir, name)
                if os.path.exists(oldname):
                    self.msg("\t%s" % newname)
                    script = launcherScripts[name] % locals()
                    open(newname, "w").write(script)
                    os.chmod(newname, 0755)
                else:
                    self.msg("\tWarning: Cannot find %r." % oldname)
        name = 'WebKit.cgi'
        newname = os.path.join(workDir, os.path.basename(name))
        if os.path.exists(newname):
            self.msg("\t%s already exists." % newname)
            os.chmod(newname, 0755)
        else:
            oldname = os.path.join(webKitDir, os.path.join('Adapters', name))
            if os.path.exists(oldname):
                self.msg("\t%s" % newname)
                script = open(oldname, 'r').read()
                script = re.sub('^#!/usr/bin/env python\n',
                    '#!%s\n' % executable, script, 1)
                parameter = (('workDir', workDir), ('webwareDir', webwareDir))
                for p in parameter:
                    pattern, repl = '\n%s = .*\n' % p[0], '\n%s = %r\n' % p
                    script, n = re.subn(pattern, repl, script, 1)
                    if n != 1:
                        self.msg("\tWarning: %s cannot be set in %s."
                            % (p[0], name))
                open(newname, 'w').write(script)
                os.chmod(newname, 0755)
            else:
                self.msg("\tWarning: Cannot find %r." % oldname)
        self.msg()

    def makeDefaultContext(self):
        """Make a very simple context for the newbie user to play with."""
        self.msg("Creating default context...")
        contextDir = os.path.join(
            self._workDir,
            self._contextDir or self._contextName)
        if contextDir.startswith(self._workDir):
            configDir = contextDir[len(self._workDir):]
            while configDir[:1] in (os.sep, os.altsep):
                configDir = configDir[1:]
        else:
            configDir = contextDir
        if os.path.exists(contextDir):
            self.msg("\t%s already exists." % contextDir)
        else:
            self.msg("\t%s" % contextDir)
            os.makedirs(contextDir)
        for name in exampleContext:
            filename = os.path.join(contextDir, name)
            if os.path.exists(filename):
                self.msg("\t%s already exists." % filename)
            else:
                self.msg("\t%s" % filename)
                open(filename, "w").write(exampleContext[name])
        self.msg("Updating config for default context...")
        filename = os.path.join(self._workDir, 'Configs',
            'Application.config')
        self.msg("\t%s" % filename)
        content = open(filename).readlines()
        output = open(filename, 'w')
        foundContext = 0
        for line in content:
            if line.startswith("Contexts[%r] = %r\n"
                    % (self._contextName, configDir)):
                foundContext += 1
            elif line.startswith("Contexts['default'] = "):
                output.write("Contexts[%r] = %r\n"
                    % (self._contextName, configDir))
                output.write("Contexts['default'] = %r\n"
                    % self._contextName)
                foundContext += 2
            else:
                output.write(line)
        if foundContext < 2:
            self.msg("\tWarning: Default context could not be set.")
        self.msg()

    def addCvsIgnore(self):
        self.msg("Creating .cvsignore files...")
        files = {
            '.': '*.pyc\n*.pyo\n'
                'address.*\nhttpd.*\nappserverpid.*\nprofile.pstats',
            'Cache': '[a-zA-Z0-9]*',
            'ErrorMsgs': '[a-zA-Z0-9]*',
            'Logs': '[a-zA-Z0-9]*',
            'Sessions': '[a-zA-Z0-9]*',
            self._contextName: '*.pyc\n*.pyo'
        }
        foundFiles = False
        for dir, contents in files.items():
            filename = os.path.join(self._workDir,
                dir, '.cvsignore')
            if os.path.exists(filename):
                foundFiles = True
            else:
                f = open(filename, 'w')
                f.write(contents)
                f.close()
        if foundFiles:
            self.msg("\tDid not change existing .cvsignore files.")
        self.msg()

    def changeOwner(self):
        if self._uid is None and self._gid is None:
            return
        self.msg("Changing the ownership...")
        uid = self._uid
        if uid is None:
            uid = os.getuid()
        gid = self._gid
        if gid is None:
            gid = os.getgid()
        try:
            os.chown(self._workDir, uid, gid)
        except Exception:
            self.msg("\tWarning: The ownership could not be changed.")
        else:
            for dir, dirs, files in os.walk(self._workDir):
                for file in dirs + files:
                    path = os.path.join(dir, file)
                    os.chown(path, uid, gid)
        self.msg()

    def printCompleted(self):
        run = os.path.abspath(os.path.join(self._workDir, 'AppServer'))
        print """
Congratulations, you've just created a runtime working directory for Webware.

To start the app server you can run this command:

%s

By default the built-in HTTP server is activated. So you can immediately see
an example that has been generated for you to play with and to build upon by
pointing your browser to:

http://localhost:8080

In a productive environment, you will probably want to use Apache or another
web server instead of the built-in HTTP server. The most simple (but least
performant) solution to do this is by using the Python WebKit.cgi CGI script.
Copy it to your web server's cgi-bin directory or anywhere else that it will
execute CGIs from. If you see import errors, you may need to modify the file
permissions on your Webware directory so that the CGI script can access it.

Have fun!
""" % run

    def msg(self, text=None):
        if self._verbose:
            if text:
                print text
            else:
                print

launcherScripts = { # launcher scripts with adjusted parameters

'Launch.py': r"""#!%(executable)s

# You can pass several parameters on the command line
# (more info by running this with option --help)
# or you can modify the default values here
# (more info in WebKit.Launch):

workDir = None
webwareDir = %(webwareDir)r
libraryDirs = %(libraryDirs)r
runProfile = False
logFile = None
pidFile = None
user = %(user)r
group = %(group)r

import sys
sys.path.insert(0, webwareDir)

from WebKit import Launch

Launch.workDir = workDir
Launch.webwareDir = webwareDir
Launch.libraryDirs = libraryDirs
Launch.runProfile = runProfile
Launch.logFile = logFile
Launch.pidFile = pidFile
Launch.user = user
Launch.group = group

if __name__ == '__main__':
    Launch.main()
""",

'AppServerService.py': r"""#!%(executable)s

# You can adjust several parameters here
# (more info in WebKit.AppServerService):

workDir = None
webwareDir = %(webwareDir)r
libraryDirs = %(libraryDirs)r
runProfile = False
logFile = None
appServer = 'ThreadedAppServer'
serviceName = 'WebKit'
serviceDisplayName = 'WebKit Application Server'
serviceDescription = ("This is the threaded application server"
    " of the Webware for Python web framework.")
serviceDeps = []

import sys, os
sys.path.insert(0, webwareDir)

from WebKit import AppServerService as service

class AppServerService(service.AppServerService):
    # this class must be defined here in __main__
    _svc_name_ = serviceName
    _svc_display_name_ = serviceDisplayName
    _svc_description_ = serviceDescription
    _svc_deps_ = serviceDeps
    _workDir = workDir or os.path.dirname(__file__)
    _webwareDir = webwareDir
    _libraryDirs = libraryDirs
    _runProfile = runProfile
    _logFile = logFile
    _appServer = appServer

if __name__ == '__main__':
    service.AppServerService = AppServerService
    service.main()
"""

} # end of launcher scripts

exampleContext = { # files copied to example context

# This is used to create a very simple sample context for the new
# work dir to give the newbie something easy to play with.

'__init__.py': r"""
def contextInitialize(appServer, path):
    # You could put initialization code here to be executed
    # when the context is loaded into WebKit.
    pass
""",

'Main.py': r"""
from WebKit.Page import Page

class Main(Page):

    def title(self):
        return 'My Sample Context'

    def writeContent(self):
        self.writeln('<h1>Welcome to Webware for Python!</h1>')
        self.writeln('''
        <p>This is a sample context generated for you and has purposly been kept
        very simple to give you something to play with to get yourself started.
        The code that implements this page is located in <b>%s</b>.</p>
        ''' % self.request().serverSidePath())
        self.writeln('''
        <p>There are more examples and documentation in the Webware distribution,
        which you can get to from here:</p>
        <ul>
        ''')
        servletPath = self.request().servletPath()
        contextName = self.request().contextName()
        for ctx in sorted(self.application().contexts()):
            if ctx in ('default', contextName) or '/' in ctx:
                continue
            self.writeln('<li><a href="%s/%s/">%s</a></li>'
                % (servletPath, ctx, ctx))
        self.writeln('</ul>')
"""

} # end of example context files

def usage():
    """Print the docstring and exit with error."""
    print __doc__
    sys.exit(2)

def main(args=None):
    """Evaluate the command line arguments and call MakeAppWorkDir."""
    if args is None:
        args = sys.argv[1:]
    contextName = contextDir = cvsIgnore = user = group = None
    libraryDirs = []
    # Get all options:
    from getopt import getopt, GetoptError
    try:
        opts, args = getopt(args, 'c:d:l:iu:g:', [
            'context-name=', 'context-dir=', 'library=',
            'cvsignore', 'user=', 'group='])
    except GetoptError, error:
        print str(error)
        usage()
    for opt, arg in opts:
        if opt in ('-c', '--context-name'):
            contextName = arg
        elif opt in ('-d', '--context-dir'):
            contextDir = arg
        elif opt in ('-l', '--library'):
            libraryDirs.append(arg)
        elif opt in ('-i', '--cvsignore'):
            cvsIgnore = True
        elif opt in ('-u', '--user'):
            user = arg
        elif opt in ('-g', '--group'):
            group = arg
    # Get the name of the target directory:
    try:
        workDir = args.pop(0)
    except IndexError:
        usage()
    if args:# too many parameters
        usage()
    if not contextName:
        if contextDir:
            contextName = os.path.basename(contextDir)
        else:
            contextName = 'MyContext'
    # Figure out the group id:
    gid = group
    if gid is not None:
        try:
            gid = int(gid)
        except ValueError:
            try:
                import grp
                entry = grp.getgrnam(gid)
            except KeyError:
                print 'Error: Group %r does not exist.' % gid
                sys.exit(2)
            except ImportError:
                print 'Error: Group names are supported under Unix only.'
                sys.exit(2)
            gid = entry[2]
    # Figure out the user id:
    uid = user
    if uid is not None:
        try:
            uid = int(uid)
        except ValueError:
            try:
                import pwd
                entry = pwd.getpwnam(uid)
            except KeyError:
                print 'Error: User %r does not exist.' % uid
                sys.exit(2)
            except ImportError:
                print 'Error: User names are supported under Unix only.'
                sys.exit(2)
            if not gid:
                gid = entry[3]
            uid = entry[2]
    # This assumes that this script is still located in Webware/bin:
    scriptName = sys.argv and sys.argv[0]
    if not scriptName or scriptName == '-c':
        scriptName = 'MakeAppWorkDir.py'
    binDir = os.path.dirname(os.path.abspath(scriptName))
    webwareDir = os.path.abspath(os.path.join(binDir, os.pardir))
    mawd = MakeAppWorkDir(webwareDir, workDir, True, None,
        contextName, contextDir, libraryDirs, cvsIgnore, uid, gid)
    mawd.buildWorkDir() # go!

if __name__ == '__main__':
    main()