[CalendarServer-changes] [525] CalendarServer/branches/caladmin-tool
source_changes at macosforge.org
source_changes at macosforge.org
Mon Nov 20 10:12:09 PST 2006
Revision: 525
http://trac.macosforge.org/projects/calendarserver/changeset/525
Author: dreid at apple.com
Date: 2006-11-20 10:12:09 -0800 (Mon, 20 Nov 2006)
Log Message:
-----------
Add basic administration cmd line tool
Added Paths:
-----------
CalendarServer/branches/caladmin-tool/bin/caladmin
CalendarServer/branches/caladmin-tool/caladmin/
CalendarServer/branches/caladmin-tool/caladmin/__init__.py
CalendarServer/branches/caladmin-tool/caladmin/caldav.py
CalendarServer/branches/caladmin-tool/caladmin/commands.py
CalendarServer/branches/caladmin-tool/caladmin/formatters.py
CalendarServer/branches/caladmin-tool/caladmin/purge.py
CalendarServer/branches/caladmin-tool/caladmin/script.py
CalendarServer/branches/caladmin-tool/caladmin/users.py
Added: CalendarServer/branches/caladmin-tool/bin/caladmin
===================================================================
--- CalendarServer/branches/caladmin-tool/bin/caladmin (rev 0)
+++ CalendarServer/branches/caladmin-tool/bin/caladmin 2006-11-20 18:12:09 UTC (rev 525)
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+import sys, os
+
+sys.path.insert(0, os.path.join(os.environ['HOME'],
+ 'projects', 'CalendarServer',
+ 'Twisted'))
+
+if __name__ == '__main__':
+ from caladmin.script import run
+ run()
Property changes on: CalendarServer/branches/caladmin-tool/bin/caladmin
___________________________________________________________________
Name: svn:executable
+ *
Added: CalendarServer/branches/caladmin-tool/caladmin/__init__.py
===================================================================
Added: CalendarServer/branches/caladmin-tool/caladmin/caldav.py
===================================================================
--- CalendarServer/branches/caladmin-tool/caladmin/caldav.py (rev 0)
+++ CalendarServer/branches/caladmin-tool/caladmin/caldav.py 2006-11-20 18:12:09 UTC (rev 525)
@@ -0,0 +1,117 @@
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+import os
+
+from urlparse import urlparse
+
+from zanshin.webdav import ServerHandle, PropfindRequest
+from zanshin.util import PackElement
+
+from twisted.web import microdom
+
+defaultPorts = {'https': 443,
+ 'http': 80}
+
+def makeHandle(url, username=None, password=None):
+ """Get a ServerHandle for the given url
+ """
+ scheme, server, path, parameters, query, fragment, = urlparse(url)
+
+ port = defaultPorts[scheme]
+
+ serverPortList = server.split(':', 1)
+ server = serverPortList[0]
+ if len(serverPortList) > 1:
+ port = int(serverPortList[1])
+
+ sh = ServerHandle(host=server,
+ port=port,
+ username=username,
+ password=password)
+
+ return sh
+
+
+def parseQuotaPropfind(response):
+ """Generator that yeilds:
+ name - a string as derived from basepath on the returned uri.
+ total - an integer equal to the available + the used
+ used - an integer from quota-used-bytes
+ avail - an integer from quota-available-bytes
+
+ total, used, and available values are given as # of bytes.
+ """
+
+ dom = microdom.parseString(response.body)
+
+ for resp in microdom.getElementsByTagName(dom, 'response'):
+ href = resp.getElementsByTagName('href'
+ )[0].firstChild().toxml()
+
+ avail = resp.getElementsByTagName('quota-available-bytes'
+ )[0].firstChild()
+ used = resp.getElementsByTagName('quota-used-bytes'
+ )[0].firstChild()
+
+ name = href.split('/')[-2]
+
+ if name in ('users', 'groups', 'resources'):
+ continue
+
+ if not avail:
+ avail = 0
+ else:
+ avail = int(avail.toxml())
+
+ if not used:
+ used = 0
+ else:
+ used = int(used.toxml())
+
+ yield (name, avail+used, used, avail)
+
+
+def getQuotaStats(handle, type, entity=None):
+ """Utility function for getting a generator as described in
+ parseQuotaPropfind given a server handle for the given type
+ and possibly a given entity (username, groupname or resource name.)
+ """
+
+ path = os.path.join('/calendars', type)
+ depth = '1'
+
+ if entity:
+ path = os.path.join(path, entity)
+ depth = '0'
+
+ quotaProps = [PackElement(x) for x in ['quota-available-bytes',
+ 'quota-used-bytes']]
+
+ request = PropfindRequest(
+ path,
+ depth,
+ quotaProps,
+ None)
+
+ d = handle.addRequest(request)
+ d.addCallback(parseQuotaPropfind)
+
+ return d
+
+
Added: CalendarServer/branches/caladmin-tool/caladmin/commands.py
===================================================================
--- CalendarServer/branches/caladmin-tool/caladmin/commands.py (rev 0)
+++ CalendarServer/branches/caladmin-tool/caladmin/commands.py 2006-11-20 18:12:09 UTC (rev 525)
@@ -0,0 +1,127 @@
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+""" "pluggable" subcommands for the caladmin script
+"""
+
+from twisted.python import usage
+
+COMMANDS = {}
+
+def registerCommand(command):
+ COMMANDS[command.name] = command
+
+def listCommands():
+ return COMMANDS.keys()
+
+def genSubCommandsDef():
+ sc = listCommands()
+ sc.sort()
+
+ for name in sc:
+ command = COMMANDS[name]
+ yield [command.name, command.shortcut, command, command.help]
+
+
+# Some common parameter definitions
+
+PARAM_DOCROOT = ['docroot', 'D', '/Library/CalendarServer/Documents',
+ 'Document root for the calendar server data to back up.']
+
+
+class SubCommand(usage.Options):
+ name = None
+ shortcut = None
+ help = "FIXME"
+ action = None
+
+ params = ()
+
+ def parseArgs(self, *rest):
+ self.params += rest
+
+ def postOptions(self):
+ self.action(self).run()
+
+
+from twisted.internet import reactor
+from twisted.internet.defer import maybeDeferred
+from twisted.python.failure import Failure
+
+class TwistedSubCommand(SubCommand):
+ """Subcommand subclass that calls it's action's run method from within a
+ reactor."""
+
+ def postOptions(self):
+
+ def _log(failure):
+ failure.printTraceback()
+
+ def _runRun():
+ try:
+ d = maybeDeferred(self.action(self).run)
+ d.addErrback(_log).addBoth(lambda _: reactor.stop())
+ except:
+ failure = Failure()
+ failure.printTraceback()
+
+ reactor.stop()
+
+
+ reactor.callLater(0, _runRun)
+ reactor.run()
+
+
+from caladmin.users import UserAction
+
+class UserOptions(TwistedSubCommand):
+ name = 'users'
+ help = 'Retrieve information about and perform actions on users.'
+ action = UserAction
+
+ optFlags = [
+ ['list', '1', 'List only usernames, one per line.'],
+ ['disabled', 'd', 'Limit display to disabled users.']
+ ]
+
+ optParameters = [
+ ['server', 's', 'http://localhost:8008/',
+ 'The url of the calendar server to query for user information.'],
+ ['username', 'u', None,
+ 'The username to connect to the calendar server'],
+ ['password', 'p', None,
+ 'The password'],
+ ]
+
+registerCommand(UserOptions)
+
+
+from purge import PurgeAction
+
+class PurgeOptions(SubCommand):
+ name = 'purge'
+ help = ('Keep your store from becoming unnecessarily large by purging '
+ 'old events.')
+ action = PurgeAction
+
+ optParameters = [
+ ['days', 'n', 30, 'Age threshold for purging events.'],
+ PARAM_DOCROOT
+ ]
+
+registerCommand(PurgeOptions)
Added: CalendarServer/branches/caladmin-tool/caladmin/formatters.py
===================================================================
--- CalendarServer/branches/caladmin-tool/caladmin/formatters.py (rev 0)
+++ CalendarServer/branches/caladmin-tool/caladmin/formatters.py 2006-11-20 18:12:09 UTC (rev 525)
@@ -0,0 +1,62 @@
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+import sys
+
+FORMATTERS = {}
+
+def registerFormatter(short, formatter):
+ FORMATTERS[short] = formatter
+
+def listFormatters():
+ return FORMATTERS.keys()
+
+def getFormatter(short):
+ return FORMATTERS[short]
+
+
+class BaseFormatter(object):
+ def __init__(self, dst=None):
+ self.dst = dst
+
+ if not self.dst:
+ self.dst = sys.stdout
+
+
+class PlainFormatter(BaseFormatter):
+ def printRow(self, row, spacelen):
+ for el in row:
+ self.dst.write(str(el))
+ self.dst.write(' '*(spacelen - len(str(el))))
+
+ self.dst.write('\n')
+
+registerFormatter('plain', PlainFormatter)
+
+
+class CsvFormatter(BaseFormatter):
+ def printRow(self, row, spacelen):
+ for el in row:
+ self.dst.write(str(el))
+ self.dst.write(',')
+
+ self.dst.write('\n')
+
+registerFormatter('csv', CsvFormatter)
+
+
Added: CalendarServer/branches/caladmin-tool/caladmin/purge.py
===================================================================
--- CalendarServer/branches/caladmin-tool/caladmin/purge.py (rev 0)
+++ CalendarServer/branches/caladmin-tool/caladmin/purge.py 2006-11-20 18:12:09 UTC (rev 525)
@@ -0,0 +1,115 @@
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+import os
+
+import datetime, dateutil.tz
+
+def purge(collection, purgeDate):
+ """
+ Recursively purge all events older than purgeDate.
+
+ for VTODO:
+ * if completed
+ * purge if it's dueDate is older than purgeDate.
+
+ for V*:
+ * purge if endDate is older than purgeDate
+ """
+
+ from twistedcaldav import ical
+
+ collection = os.path.abspath(collection)
+
+ files = []
+ directories = []
+
+ for child in os.listdir(collection):
+ if child == '.db.sqlite':
+ continue
+
+ child = os.path.join(collection, child)
+
+ if os.path.isdir(child):
+ directories.append(child)
+
+ elif os.path.isfile(child):
+ files.append(child)
+
+ for directory in directories:
+ purge(directory, purgeDate)
+
+ for fname in files:
+ f = open(fname)
+
+ try:
+ component = ical.Component.fromStream(f)
+ except ValueError:
+ # Not a calendar file?
+ continue
+
+ f.close()
+
+ endDate = component.mainComponent().getEndDateUTC()
+
+ if component.resourceType() == 'VTODO':
+ if component.mainComponent().hasProperty('COMPLETED'):
+ endDate = component.mainComponent().getDueDateUTC()
+ else:
+ endDate = None
+
+ if isinstance(endDate, datetime.datetime):
+ endDate = endDate.date()
+
+ if endDate:
+ if purgeDate > endDate:
+ print "Purging %s, %s, %s" % (component.resourceType(),
+ component.resourceUID(),
+ endDate.isoformat())
+ os.remove(fname)
+
+
+class PurgeAction(object):
+ def __init__(self, config):
+ self.config = config
+
+ def run(self):
+ assert os.path.exists(self.config['docroot'])
+
+ calendarCollectionRoot = os.path.join(
+ os.path.abspath(self.config['docroot']),
+ 'calendars')
+
+ if self.config.params:
+ collections = [os.path.join(calendarCollectionRoot, p)
+ for p in self.config.params]
+
+ else:
+ collections = []
+
+ for type in os.listdir(calendarCollectionRoot):
+ tRoot = os.path.join(calendarCollectionRoot, type)
+
+ for collection in os.listdir(tRoot):
+ collections.append(os.path.join(tRoot, collection))
+
+ purgeDate = datetime.date.today()
+ purgeDate = purgeDate - datetime.timedelta(self.config['days'])
+
+ for collection in collections:
+ purge(collection, purgeDate)
Added: CalendarServer/branches/caladmin-tool/caladmin/script.py
===================================================================
--- CalendarServer/branches/caladmin-tool/caladmin/script.py (rev 0)
+++ CalendarServer/branches/caladmin-tool/caladmin/script.py 2006-11-20 18:12:09 UTC (rev 525)
@@ -0,0 +1,93 @@
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+"""
+Examples:
+
+ caladmin users
+
+ caladmin purge
+
+ caladmin backup
+
+ caladmin restore
+"""
+
+import sys, os
+
+from twisted.python import usage
+
+from caladmin import commands
+from caladmin import formatters
+
+class AdminOptions(usage.Options):
+ recursing = 0
+ params = ()
+
+ optParameters = [
+ ['format', 'f', 'plain', "Select an appropriate output formatter: %s" % (formatters.listFormatters())]
+ ]
+
+ def parseArgs(self, *rest):
+ self.params += rest
+
+ def postOptions(self):
+ if self.recursing:
+ return
+
+ lf = formatters.listFormatters()
+ lf.sort()
+
+ if self['format'] in lf:
+ self.formatter = formatters.getFormatter(self['format'])()
+ else:
+ raise usage.UsageError("Please specify a valid formatter: %s" % (
+ ', '.join(lf)))
+
+ sc = commands.listCommands()
+ sc.sort()
+
+ self.subCommands = commands.genSubCommandsDef()
+
+ self.recursing = 1
+
+ self.parseOptions(self.params)
+
+ if self.subCommand not in sc:
+ raise usage.UsageError("Please select one of: %s" % (
+ ', '.join(sc)))
+
+
+def run():
+ config = AdminOptions()
+
+ try:
+ config.parseOptions()
+
+ except usage.UsageError, ue:
+ print config
+ if len(sys.argv) > 1:
+ cmd = sys.argv[1]
+ else:
+ cmd = sys.argv[0]
+
+ print "%s: %s" % (cmd, ue)
+
+
+ except KeyboardInterrupt:
+ sys.exit(1)
Added: CalendarServer/branches/caladmin-tool/caladmin/users.py
===================================================================
--- CalendarServer/branches/caladmin-tool/caladmin/users.py (rev 0)
+++ CalendarServer/branches/caladmin-tool/caladmin/users.py 2006-11-20 18:12:09 UTC (rev 525)
@@ -0,0 +1,79 @@
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: David Reid, dreid at apple.com
+##
+
+from caladmin import caldav
+from twisted.python.urlpath import URLPath
+
+class UserAction(object):
+ def __init__(self, config):
+ self.config = config
+ self.formatter = self.config.parent.formatter
+
+ self.users = list(self.config.params)
+
+ def printQuotaHead(self):
+ if not self.config['list']:
+ self.formatter.printRow(['Name', 'Quota', 'Used', 'Available'], 16)
+
+
+ def printRecord(self, record):
+ if self.config['list']:
+ self.formatter.printRow([record[0]], 0)
+ else:
+ self.formatter.printRow(record, 16)
+
+ def printRecords(self, records):
+ self.printQuotaHead()
+
+ for record in records:
+ self.printRecord(record)
+
+ def run(self):
+ sh = caldav.makeHandle(self.config['server'],
+ self.config['username'],
+ self.config['password'])
+
+ if not self.users:
+ d = caldav.getQuotaStats(sh, 'users')
+ d.addCallback(self.printRecords)
+
+ else:
+ users = self.users
+
+ def _getNextUser(ign):
+ if users:
+ user = users.pop(0)
+
+ return _getUser(user)
+
+ def _getUser(user):
+ d = caldav.getQuotaStats(sh, 'users', user)
+ d.addCallback(lambda rec: self.printRecord(list(rec)[0]))
+ d.addCallback(_getNextUser)
+
+ return d
+
+ self.printQuotaHead()
+
+ user = users.pop(0)
+
+ d = _getUser(user)
+
+ return d
+
+
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20061120/b5bd486a/attachment.html
More information about the calendarserver-changes
mailing list