[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