[CalendarServer-changes] [10924] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Thu Mar 14 14:14:13 PDT 2013
Revision: 10924
http://trac.calendarserver.org//changeset/10924
Author: sagen at apple.com
Date: 2013-03-14 14:14:13 -0700 (Thu, 14 Mar 2013)
Log Message:
-----------
Config files can be split up between a static plist and a separate plist containing local changes, and the former includes the latter.
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/tools/config.py
CalendarServer/trunk/calendarserver/tools/gateway.py
CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist
CalendarServer/trunk/calendarserver/tools/test/test_gateway.py
CalendarServer/trunk/conf/caldavd-apple.plist
CalendarServer/trunk/setup.py
CalendarServer/trunk/support/Makefile.Apple
CalendarServer/trunk/twistedcaldav/config.py
CalendarServer/trunk/twistedcaldav/scheduling/imip/inbound.py
CalendarServer/trunk/twistedcaldav/stdconfig.py
CalendarServer/trunk/twistedcaldav/test/test_config.py
Added Paths:
-----------
CalendarServer/trunk/calendarserver/tools/test/test_config.py
CalendarServer/trunk/doc/calendarserver_config.8
Removed Paths:
-------------
CalendarServer/trunk/contrib/migration/__init__.py
CalendarServer/trunk/contrib/migration/calendarcommonextra.py
CalendarServer/trunk/contrib/migration/calendardemotion.py
CalendarServer/trunk/contrib/migration/calendarmigrator.py
CalendarServer/trunk/contrib/migration/calendarpromotion.py
CalendarServer/trunk/contrib/migration/test/
Modified: CalendarServer/trunk/calendarserver/tools/config.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/config.py 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/calendarserver/tools/config.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -18,18 +18,56 @@
from __future__ import print_function
"""
-This tool reads the Calendar Server configuration file and emits the
-requested value.
+This tool gets and sets Calendar Server configuration keys
"""
-import os, sys
from getopt import getopt, GetoptError
+import os
+import plistlib
+import signal
+import sys
+import xml
-from twistedcaldav.config import ConfigurationError
-from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
+from twext.python.plistlib import readPlistFromString, writePlistToString
+from twistedcaldav.config import config, ConfigDict, ConfigurationError, mergeData
+from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE, DEFAULT_CONFIG
+WRITABLE_CONFIG_KEYS = [
+ "EnableSSL",
+ "RedirectHTTPToHTTPS",
+ "EnableCalDAV",
+ "EnableCardDAV",
+ "DataRoot",
+ "SSLCertificate",
+ "SSLPrivateKey",
+ "SSLAuthorityChain",
+ "EnableSearchAddressBook",
+ "Authentication.Basic.Enabled",
+ "Authentication.Basic.AllowedOverWireUnencrypted",
+ "Authentication.Digest.Enabled",
+ "Authentication.Digest.AllowedOverWireUnencrypted",
+ "Authentication.Kerberos.Enabled",
+ "Authentication.Kerberos.AllowedOverWireUnencrypted",
+ "Authentication.Wiki.Enabled",
+ "Scheduling.iMIP.Enabled",
+ "Scheduling.iMIP.Receiving.Username",
+ "Scheduling.iMIP.Receiving.Server",
+ "Scheduling.iMIP.Receiving.Port",
+ "Scheduling.iMIP.Receiving.Type",
+ "Scheduling.iMIP.Receiving.UseSSL",
+ "Scheduling.iMIP.Sending.Username",
+ "Scheduling.iMIP.Sending.Server",
+ "Scheduling.iMIP.Sending.Port",
+ "Scheduling.iMIP.Sending.UseSSL",
+ "Scheduling.iMIP.Sending.Address",
+ "Notifications.Services.APNS.Enabled",
+ "Notifications.Services.APNS.CalDAV.CertificatePath",
+ "Notifications.Services.APNS.CalDAV.AuthorityChainPath",
+ "Notifications.Services.APNS.CalDAV.PrivateKeyPath",
+ "Notifications.Services.APNS.CardDAV.CertificatePath",
+ "Notifications.Services.APNS.CardDAV.AuthorityChainPath",
+ "Notifications.Services.APNS.CardDAV.PrivateKeyPath",
+]
-from calendarserver.tools.util import loadConfig
-
def usage(e=None):
if e:
print(e)
@@ -42,6 +80,7 @@
print("options:")
print(" -h --help: print this help and exit")
print(" -f --config: Specify caldavd.plist configuration path")
+ print(" -w --writeconfig: Specify caldavd.plist configuration path for writing")
if e:
sys.exit(64)
@@ -51,15 +90,17 @@
def main():
try:
(optargs, args) = getopt(
- sys.argv[1:], "hf:", [
+ sys.argv[1:], "hf:w:", [
"help",
"config=",
+ "writeconfig=",
],
)
except GetoptError, e:
usage(e)
configFileName = DEFAULT_CONFIG_FILE
+ writeConfigFileName = ""
for opt, arg in optargs:
if opt in ("-h", "--help"):
@@ -68,18 +109,332 @@
elif opt in ("-f", "--config"):
configFileName = arg
+ elif opt in ("-w", "--writeconfig"):
+ writeConfigFileName = arg
+
try:
- config = loadConfig(configFileName)
+ config.load(configFileName)
except ConfigurationError, e:
sys.stdout.write("%s\n" % (e,))
sys.exit(1)
- for configKey in args:
- c = config
- for subKey in configKey.split("."):
- c = c.get(subKey, None)
- if c is None:
- sys.stderr.write("No such config key: %s\n" % configKey)
- break
+ if not writeConfigFileName:
+ # If --writeconfig was not passed, use WritableConfigFile from
+ # main plist. If that's an empty string, writes will happen to
+ # the main file.
+ writeConfigFileName = config.WritableConfigFile
+
+ if not writeConfigFileName:
+ writeConfigFileName = configFileName
+
+ writable = WritableConfig(config, writeConfigFileName)
+ writable.read()
+
+ if args:
+ for configKey in args:
+
+ if "=" in configKey:
+ # This is an assignment
+ configKey, stringValue = configKey.split("=")
+ value = writable.convertToValue(stringValue)
+ writable.set({configKey:value})
+ else:
+ # This is a read
+ c = config
+ for subKey in configKey.split("."):
+ c = c.get(subKey, None)
+ if c is None:
+ sys.stderr.write("No such config key: %s\n" % configKey)
+ break
+ sys.stdout.write("%s=%s\n" % (configKey, c))
+
+ writable.save(restart=True)
+
+ else:
+ # Read plist commands from stdin
+ rawInput = sys.stdin.read()
+ try:
+ plist = readPlistFromString(rawInput)
+ except xml.parsers.expat.ExpatError, e:
+ respondWithError(str(e))
+ return
+
+ # If the plist is an array, each element of the array is a separate
+ # command dictionary.
+ if isinstance(plist, list):
+ commands = plist
else:
- sys.stdout.write("%s\n" % c)
+ commands = [plist]
+
+ runner = Runner(commands)
+ runner.run()
+
+
+
+class Runner(object):
+
+ """
+ A class which carries out commands, which are plist strings containing
+ dictionaries with a "command" key, plus command-specific data.
+ """
+
+ def __init__(self, commands):
+ """
+ @param commands: the commands to run
+ @type commands: list of plist strings
+ """
+ self.commands = commands
+
+ def validate(self):
+ """
+ Validate all the commands by making sure this class implements
+ all the command keys.
+ @return: True if all commands are valid, False otherwise
+ """
+ # Make sure commands are valid
+ for command in self.commands:
+ if 'command' not in command:
+ respondWithError("'command' missing from plist")
+ return False
+ commandName = command['command']
+ methodName = "command_%s" % (commandName,)
+ if not hasattr(self, methodName):
+ respondWithError("Unknown command '%s'" % (commandName,))
+ return False
+ return True
+
+ def run(self):
+ """
+ Find the appropriate method for each command and call them.
+ """
+ try:
+ for command in self.commands:
+ commandName = command['command']
+ methodName = "command_%s" % (commandName,)
+ if hasattr(self, methodName):
+ getattr(self, methodName)(command)
+ else:
+ respondWithError("Unknown command '%s'" % (commandName,))
+
+ except Exception, e:
+ respondWithError("Command failed: '%s'" % (str(e),))
+ raise
+
+ def command_readConfig(self, command):
+ """
+ Return current configuration
+
+ @param command: the dictionary parsed from the plist read from stdin
+ @type command: C{dict}
+ """
+ result = {}
+ for keyPath in WRITABLE_CONFIG_KEYS:
+ value = getKeyPath(config, keyPath)
+ if value is not None:
+ setKeyPath(result, keyPath, value)
+ respond(command, result)
+
+ def command_writeConfig(self, command):
+ """
+ Write config to secondary, writable plist
+
+ @param command: the dictionary parsed from the plist read from stdin
+ @type command: C{dict}
+ """
+ writable = WritableConfig(config, config.WritableConfigFile)
+ writable.read()
+ valuesToWrite = command.get("Values", {})
+ for keyPath, value in flattenDictionary(valuesToWrite):
+ if keyPath in WRITABLE_CONFIG_KEYS:
+ writable.set(setKeyPath(ConfigDict(), keyPath, value))
+ try:
+ writable.save(restart=False)
+ except Exception, e:
+ respond(command, {"error": str(e)})
+ else:
+ config.reload()
+ self.command_readConfig(command)
+
+
+def setKeyPath(parent, keyPath, value):
+ """
+ Allows the setting of arbitrary nested dictionary keys via a single
+ dot-separated string. For example, setKeyPath(parent, "foo.bar.baz",
+ "xyzzy") would create any intermediate missing directories (or whatever
+ class parent is, such as ConfigDict) so that the following structure
+ results: parent = { "foo" : { "bar" : { "baz" : "xyzzy } } }
+
+ @param parent: the object to modify
+ @type parent: any dict-like object
+ @param keyPath: a dot-delimited string specifying the path of keys to
+ traverse
+ @type keyPath: C{str}
+ @param value: the value to set
+ @type value: c{object}
+ @return: parent
+ """
+ original = parent
+ parts = keyPath.split(".")
+ for part in parts[:-1]:
+ child = parent.get(part, None)
+ if child is None:
+ parent[part] = child = parent.__class__()
+ parent = child
+ parent[parts[-1]] = value
+ return original
+
+def getKeyPath(parent, keyPath):
+ """
+ Allows the getting of arbitrary nested dictionary keys via a single
+ dot-separated string. For example, getKeyPath(parent, "foo.bar.baz")
+ would fetch parent["foo"]["bar"]["baz"]. If any of the keys don't
+ exist, None is returned instead.
+
+ @param parent: the object to traverse
+ @type parent: any dict-like object
+ @param keyPath: a dot-delimited string specifying the path of keys to
+ traverse
+ @type keyPath: C{str}
+ @return: the value at keyPath
+ """
+ parts = keyPath.split(".")
+ for part in parts[:-1]:
+ child = parent.get(part, None)
+ if child is None:
+ return None
+ parent = child
+ return parent.get(parts[-1], None)
+
+def flattenDictionary(dictionary, current=""):
+ """
+ Returns a generator of (keyPath, value) tuples for the given dictionary,
+ where each keyPath is a dot-separated string representing the complete
+ path to a nested key.
+
+ @param dictionary: the dict object to traverse
+ @type dictionary: C{dict}
+ @param current: do not use; used internally for recursion
+ @type current: C{str}
+ @return: generator of (keyPath, value) tuples
+ """
+ for key, value in dictionary.iteritems():
+ if isinstance(value, dict):
+ for result in flattenDictionary(value, current + key + "."):
+ yield result
+ else:
+ yield (current + key, value)
+
+
+def restartService(pidFilename):
+ """
+ Given the path to a PID file, sends a HUP signal to the contained pid
+ in order to cause calendar server to restart.
+
+ @param pidFilename: an absolute path to a PID file
+ @type pidFilename: C{str}
+ """
+ if os.path.exists(pidFilename):
+ pidFile = open(pidFilename, "r")
+ pid = pidFile.read().strip()
+ pidFile.close()
+ try:
+ pid = int(pid)
+ except ValueError:
+ return
+ try:
+ os.kill(pid, signal.SIGHUP)
+ except OSError:
+ pass
+
+
+class WritableConfig(object):
+ """
+ A wrapper around a Config object which allows writing of values. The idea
+ is a deployment could have a master plist which doesn't change, and have
+ it include a plist file which does. This class facilitates writing to that
+ included plist.
+ """
+
+ def __init__(self, wrappedConfig, fileName):
+ """
+ @param wrappedConfig: the Config object to read from
+ @type wrappedConfig: C{Config}
+ @param fileName: the full path to the modifiable plist
+ @type fileName: C{str}
+ """
+ self.config = wrappedConfig
+ self.fileName = fileName
+ self.changes = None
+ self.currentConfigSubset = ConfigDict()
+ self.dirty = False
+
+ def set(self, data):
+ """
+ Merges data into a ConfigDict of changes intended to be saved to disk
+ when save( ) is called.
+
+ @param data: a dict containing new values
+ @type data: C{dict}
+ """
+ if not isinstance(data, ConfigDict):
+ data = ConfigDict(mapping=data)
+ mergeData(self.currentConfigSubset, data)
+ self.dirty = True
+
+ def read(self):
+ """
+ Reads in the data contained in the writable plist file.
+
+ @return: C{ConfigDict}
+ """
+ if os.path.exists(self.fileName):
+ self.currentConfigSubset = ConfigDict(mapping=plistlib.readPlist(self.fileName))
+ else:
+ self.currentConfigSubset = ConfigDict()
+
+ def toString(self):
+ return plistlib.writePlistToString(self.currentConfigSubset)
+
+ def save(self, restart=False):
+ """
+ Writes any outstanding changes to the writable plist file. Optionally
+ restart calendar server.
+
+ @param restart: whether to restart the calendar server.
+ @type restart: C{bool}
+ """
+ if self.dirty:
+ plistlib.writePlist(self.currentConfigSubset, self.fileName)
+ self.dirty = False
+ if restart:
+ restartService(self.config.PIDFile)
+
+ @classmethod
+ def convertToValue(cls, string):
+ """
+ Inspect string and convert the value into an appropriate Python data type
+ TODO: change this to look at actual types definied within stdconfig
+ """
+ if "." in string:
+ try:
+ value = float(string)
+ except ValueError:
+ value = string
+ else:
+ try:
+ value = int(string)
+ except ValueError:
+ if string == "True":
+ value = True
+ elif string == "False":
+ value = False
+ else:
+ value = string
+ return value
+
+
+def respond(command, result):
+ sys.stdout.write(writePlistToString({'command' : command['command'], 'result' : result}))
+
+def respondWithError(msg, status=1):
+ sys.stdout.write(writePlistToString({'error' : msg, }))
Modified: CalendarServer/trunk/calendarserver/tools/gateway.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/gateway.py 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/calendarserver/tools/gateway.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -465,7 +465,6 @@
respond(command, {'EventsRemoved' : eventCount, "RetainDays" : retainDays})
-
@inlineCallbacks
def respondWithProxies(directory, command, principal, proxyType):
proxies = []
@@ -508,21 +507,13 @@
respond(command, result)
-
def respond(command, result):
sys.stdout.write(writePlistToString({'command' : command['command'], 'result' : result}))
-
def respondWithError(msg, status=1):
sys.stdout.write(writePlistToString({'error' : msg, }))
- """
- try:
- reactor.stop()
- except RuntimeError:
- pass
- sys.exit(status)
- """
+
if __name__ == "__main__":
main()
Modified: CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist 2013-03-14 21:14:13 UTC (rev 10924)
@@ -34,6 +34,14 @@
<key>ServerHostName</key>
<string></string> <!-- The hostname clients use when connecting -->
+ <!-- Enable Calendars -->
+ <key>EnableCalDAV</key>
+ <true/>
+
+ <!-- Enable AddressBooks -->
+ <key>EnableCardDAV</key>
+ <true/>
+
<!-- HTTP port [0 = disable HTTP] -->
<key>HTTPPort</key>
<integer>8008</integer>
@@ -87,7 +95,7 @@
<!-- Configuration root -->
<key>ConfigRoot</key>
- <string>/etc/caldavd</string>
+ <string>config</string>
<!-- Log root -->
<key>LogRoot</key>
@@ -496,65 +504,31 @@
<key>Services</key>
<dict>
- <key>SimpleLineNotifier</key>
- <dict>
- <!-- Simple line notification service (for testing) -->
- <key>Service</key>
- <string>twistedcaldav.notify.SimpleLineNotifierService</string>
- <key>Enabled</key>
- <false/>
- <key>Port</key>
- <integer>62308</integer>
- </dict>
- <key>XMPPNotifier</key>
+ <key>APNS</key>
<dict>
- <!-- XMPP notification service -->
- <key>Service</key>
- <string>twistedcaldav.notify.XMPPNotifierService</string>
<key>Enabled</key>
<false/>
-
- <!-- XMPP host and port to contact -->
- <key>Host</key>
- <string>xmpp.host.name</string>
- <key>Port</key>
- <integer>5222</integer>
-
- <!-- Jabber ID and password for the server -->
- <key>JID</key>
- <string>jid at xmpp.host.name/resource</string>
- <key>Password</key>
- <string>password_goes_here</string>
-
- <!-- PubSub service address -->
- <key>ServiceAddress</key>
- <string>pubsub.xmpp.host.name</string>
-
- <key>NodeConfiguration</key>
+ <key>EnableStaggering</key>
+ <true/>
+ <key>StaggerSeconds</key>
+ <integer>5</integer>
+ <key>CalDAV</key>
<dict>
- <key>pubsub#deliver_payloads</key>
- <string>1</string>
- <key>pubsub#persist_items</key>
- <string>1</string>
+ <key>CertificatePath</key>
+ <string>/example/calendar.cer</string>
+ <key>PrivateKeyPath</key>
+ <string>/example/calendar.pem</string>
</dict>
-
- <!-- Sends a presence notification to XMPP server at this interval (prevents disconnect) -->
- <key>KeepAliveSeconds</key>
- <integer>120</integer>
-
- <!-- Sends a pubsub publish to a particular heartbeat node at this interval -->
- <key>HeartbeatMinutes</key>
- <integer>30</integer>
-
- <!-- List of glob-like expressions defining which XMPP JIDs can converse with the server (for debugging) -->
- <key>AllowedJIDs</key>
- <array>
- <!--
- <string>*.example.com</string>
- -->
- </array>
+ <key>CardDAV</key>
+ <dict>
+ <key>CertificatePath</key>
+ <string>/example/contacts.cer</string>
+ <key>PrivateKeyPath</key>
+ <string>/example/contacts.pem</string>
+ </dict>
</dict>
+
</dict>
</dict>
@@ -764,5 +738,12 @@
</dict>
+ <key>Includes</key>
+ <array>
+ <string>%(WritablePlist)s</string>
+ </array>
+ <key>WritableConfigFile</key>
+ <string>%(WritablePlist)s</string>
+
</dict>
</plist>
Added: CalendarServer/trunk/calendarserver/tools/test/test_config.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_config.py (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/test/test_config.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -0,0 +1,220 @@
+##
+# Copyright (c) 2013 Apple 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.
+##
+
+from twistedcaldav.test.util import TestCase
+from twistedcaldav.config import ConfigDict
+from calendarserver.tools.config import WritableConfig, setKeyPath, getKeyPath, flattenDictionary
+from calendarserver.tools.test.test_gateway import RunCommandTestCase
+from twisted.internet.defer import inlineCallbacks
+from twisted.python.filepath import FilePath
+from xml.parsers.expat import ExpatError
+import plistlib
+
+PREAMBLE = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+"""
+class WritableConfigTestCase(TestCase):
+
+ def setUp(self):
+ self.configFile = self.mktemp()
+ self.fp = FilePath(self.configFile)
+
+ def test_readSuccessful(self):
+ content = """<plist version="1.0">
+ <dict>
+ <key>string</key>
+ <string>foo</string>
+ </dict>
+</plist>"""
+ self.fp.setContent(PREAMBLE + content)
+
+ config = ConfigDict()
+ writable = WritableConfig(config, self.configFile)
+ writable.read()
+ self.assertEquals(writable.currentConfigSubset, {"string":"foo"})
+
+ def test_readInvalidXML(self):
+ self.fp.setContent("invalid")
+ config = ConfigDict()
+ writable = WritableConfig(config, self.configFile)
+ self.assertRaises(ExpatError, writable.read)
+
+ def test_updates(self):
+ content = """<plist version="1.0">
+ <dict>
+ <key>key1</key>
+ <string>before</string>
+ <key>key2</key>
+ <integer>10</integer>
+ </dict>
+</plist>"""
+ self.fp.setContent(PREAMBLE + content)
+ config = ConfigDict()
+ writable = WritableConfig(config, self.configFile)
+ writable.read()
+ writable.set({"key1":"after"})
+ writable.set({"key2":15})
+ writable.set({"key2":20}) # override previous set
+ writable.set({"key3":["a", "b", "c"]})
+ self.assertEquals(writable.currentConfigSubset, {"key1":"after", "key2":20, "key3":["a", "b", "c"]})
+ writable.save()
+
+ writable2 = WritableConfig(config, self.configFile)
+ writable2.read()
+ self.assertEquals(writable2.currentConfigSubset, {"key1":"after", "key2":20, "key3":["a", "b", "c"]})
+
+ def test_convertToValue(self):
+ self.assertEquals(True, WritableConfig.convertToValue("True"))
+ self.assertEquals(False, WritableConfig.convertToValue("False"))
+ self.assertEquals(1, WritableConfig.convertToValue("1"))
+ self.assertEquals(1.2, WritableConfig.convertToValue("1.2"))
+ self.assertEquals("xyzzy", WritableConfig.convertToValue("xyzzy"))
+ self.assertEquals("xy.zzy", WritableConfig.convertToValue("xy.zzy"))
+
+
+class ConfigTestCase(RunCommandTestCase):
+
+ @inlineCallbacks
+ def test_readConfig(self):
+ """
+ Verify readConfig returns with only the writable keys
+ """
+ results = yield self.runCommand(command_readConfig,
+ script="calendarserver_config")
+
+ self.assertEquals(results["result"]["RedirectHTTPToHTTPS"], False)
+ self.assertEquals(results["result"]["EnableSearchAddressBook"], False)
+ self.assertEquals(results["result"]["EnableCalDAV"], True)
+ self.assertEquals(results["result"]["EnableCardDAV"], True)
+ self.assertEquals(results["result"]["EnableSSL"], False)
+ self.assertEquals(results["result"]["Notifications"]["Services"]["APNS"]["Enabled"], False)
+ self.assertEquals(results["result"]["Notifications"]["Services"]["APNS"]["CalDAV"]["CertificatePath"], "/example/calendar.cer")
+
+ # Verify not all keys are present, such as ServerRoot which is not writable
+ self.assertFalse(results["result"].has_key("ServerRoot"))
+
+ @inlineCallbacks
+ def test_writeConfig(self):
+ """
+ Verify writeConfig updates the writable plist file only
+ """
+ results = yield self.runCommand(command_writeConfig,
+ script="calendarserver_config")
+
+ self.assertEquals(results["result"]["EnableCalDAV"], False)
+ self.assertEquals(results["result"]["EnableCardDAV"], False)
+ self.assertEquals(results["result"]["EnableSSL"], True)
+ self.assertEquals(results["result"]["Notifications"]["Services"]["APNS"]["Enabled"], True)
+ self.assertEquals(results["result"]["Notifications"]["Services"]["APNS"]["CalDAV"]["CertificatePath"], "/example/changed.cer")
+
+ # The static plist should still have EnableCalDAV = True
+ staticPlist = plistlib.readPlist(self.configFileName)
+ self.assertTrue(staticPlist["EnableCalDAV"])
+
+ @inlineCallbacks
+ def test_error(self):
+ """
+ Verify sending a bogus command returns an error
+ """
+ results = yield self.runCommand(command_bogusCommand,
+ script="calendarserver_config")
+ self.assertEquals(results["error"], "Unknown command 'bogus'")
+
+
+ def test_keyPath(self):
+ d = ConfigDict()
+ setKeyPath(d, "one", "A")
+ setKeyPath(d, "one", "B")
+ setKeyPath(d, "two.one", "C")
+ setKeyPath(d, "two.one", "D")
+ setKeyPath(d, "two.two", "E")
+ setKeyPath(d, "three.one.one", "F")
+ setKeyPath(d, "three.one.two", "G")
+
+ self.assertEquals(d.one, "B")
+ self.assertEquals(d.two.one, "D")
+ self.assertEquals(d.two.two, "E")
+ self.assertEquals(d.three.one.one, "F")
+ self.assertEquals(d.three.one.two, "G")
+
+ self.assertEquals(getKeyPath(d, "one"), "B")
+ self.assertEquals(getKeyPath(d, "two.one"), "D")
+ self.assertEquals(getKeyPath(d, "two.two"), "E")
+ self.assertEquals(getKeyPath(d, "three.one.one"), "F")
+ self.assertEquals(getKeyPath(d, "three.one.two"), "G")
+
+ def test_flattenDictionary(self):
+ dictionary = {
+ "one" : "A",
+ "two" : {
+ "one" : "D",
+ "two" : "E",
+ },
+ "three" : {
+ "one" : {
+ "one" : "F",
+ "two" : "G",
+ },
+ },
+ }
+ self.assertEquals(
+ set(list(flattenDictionary(dictionary))),
+ set([("one", "A"), ("three.one.one", "F"), ("three.one.two", "G"), ("two.one", "D"), ("two.two", "E")])
+ )
+
+
+command_readConfig = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>command</key>
+ <string>readConfig</string>
+</dict>
+</plist>
+"""
+
+command_writeConfig = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>command</key>
+ <string>writeConfig</string>
+ <key>Values</key>
+ <dict>
+ <key>EnableCalDAV</key>
+ <false/>
+ <key>EnableCardDAV</key>
+ <false/>
+ <key>EnableSSL</key>
+ <true/>
+ <key>Notifications.Services.APNS.Enabled</key>
+ <true/>
+ <key>Notifications.Services.APNS.CalDAV.CertificatePath</key>
+ <string>/example/changed.cer</string>
+ </dict>
+</dict>
+</plist>
+"""
+
+command_bogusCommand = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>command</key>
+ <string>bogus</string>
+</dict>
+</plist>
+"""
Modified: CalendarServer/trunk/calendarserver/tools/test/test_gateway.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_gateway.py 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/calendarserver/tools/test/test_gateway.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -29,10 +29,10 @@
from calendarserver.tools.util import getDirectory
-class GatewayTestCase(TestCase):
+class RunCommandTestCase(TestCase):
def setUp(self):
- super(GatewayTestCase, self).setUp()
+ super(RunCommandTestCase, self).setUp()
testRoot = os.path.join(os.path.dirname(__file__), "gateway")
templateName = os.path.join(testRoot, "caldavd.plist")
@@ -42,6 +42,7 @@
newConfig = template % {
"ServerRoot" : os.path.abspath(config.ServerRoot),
+ "WritablePlist" : os.path.join(os.path.abspath(config.ConfigRoot), "caldavd-writable.plist"),
}
configFilePath = FilePath(os.path.join(config.ConfigRoot, "caldavd.plist"))
configFilePath.setContent(newConfig)
@@ -71,7 +72,8 @@
return d
@inlineCallbacks
- def runCommand(self, command, error=False):
+ def runCommand(self, command, error=False,
+ script="calendarserver_command_gateway"):
"""
Run the given command by feeding it as standard input to
calendarserver_command_gateway in a subprocess.
@@ -82,9 +84,9 @@
sourceRoot = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
python = sys.executable
- gateway = os.path.join(sourceRoot, "bin", "calendarserver_command_gateway")
+ script = os.path.join(sourceRoot, "bin", script)
- args = [python, gateway, "-f", self.configFileName]
+ args = [python, script, "-f", self.configFileName]
if error:
args.append("--error")
@@ -101,6 +103,9 @@
returnValue(plist)
+
+class GatewayTestCase(RunCommandTestCase):
+
@inlineCallbacks
def test_getLocationList(self):
results = yield self.runCommand(command_getLocationList)
@@ -287,6 +292,7 @@
self.assertEquals(results["result"]["RetainDays"], 365)
+
command_addReadProxy = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
Modified: CalendarServer/trunk/conf/caldavd-apple.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-apple.plist 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/conf/caldavd-apple.plist 2013-03-14 21:14:13 UTC (rev 10924)
@@ -522,5 +522,13 @@
<key>OpenDirectoryModule</key>
<string>calendarserver.platform.darwin.od.opendirectory</string>
+ <key>Includes</key>
+ <array>
+ <string>/Library/Server/Calendar and Contacts/Config/caldavd-system.plist</string>
+ <string>/Library/Server/Calendar and Contacts/Config/caldavd-user.plist</string>
+ </array>
+ <key>WritableConfigFile</key>
+ <string>/Library/Server/Calendar and Contacts/Config/caldavd-system.plist</string>
+
</dict>
</plist>
Deleted: CalendarServer/trunk/contrib/migration/__init__.py
===================================================================
--- CalendarServer/trunk/contrib/migration/__init__.py 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/contrib/migration/__init__.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -1,15 +0,0 @@
-##
-# Copyright (c) 2010-2013 Apple 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.
-##
Deleted: CalendarServer/trunk/contrib/migration/calendarcommonextra.py
===================================================================
--- CalendarServer/trunk/contrib/migration/calendarcommonextra.py 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/contrib/migration/calendarcommonextra.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -1,187 +0,0 @@
-#!/usr/bin/env python
-#
-# CommonExtra script for calendar server.
-#
-# Copyright (c) 2012-2013 Apple Inc. All Rights Reserved.
-#
-# IMPORTANT NOTE: This file is licensed only for use on Apple-labeled
-# computers and is subject to the terms and conditions of the Apple
-# Software License Agreement accompanying the package this file is a
-# part of. You may not port this file to another platform without
-# Apple's written consent.
-from __future__ import print_function
-
-import datetime
-import subprocess
-from plistlib import readPlist, writePlist
-
-LOG = "/Library/Logs/Migration/calendarmigrator.log"
-SERVER_APP_ROOT = "/Applications/Server.app/Contents/ServerRoot"
-CALENDAR_SERVER_ROOT = "/Library/Server/Calendar and Contacts"
-CALDAVD_PLIST = "%s/Config/caldavd.plist" % (CALENDAR_SERVER_ROOT,)
-SERVER_ADMIN = "%s/usr/sbin/serveradmin" % (SERVER_APP_ROOT,)
-CERT_ADMIN = "/Applications/Server.app/Contents/ServerRoot/usr/sbin/certadmin"
-PGDUMP = "%s/usr/bin/pg_dump" % (SERVER_APP_ROOT,)
-DROPDB = "%s/usr/bin/dropdb" % (SERVER_APP_ROOT,)
-POSTGRES_SERVICE_NAME = "postgres_server"
-PGSOCKETDIR = "/Library/Server/PostgreSQL For Server Services/Socket"
-USERNAME = "caldav"
-DATABASENAME = "caldav"
-DATADUMPFILENAME = "%s/DataDump.sql" % (CALENDAR_SERVER_ROOT,)
-
-def log(msg):
- try:
- timestamp = datetime.datetime.now().strftime("%b %d %H:%M:%S")
- msg = "calendarcommonextra: %s %s" % (timestamp, msg)
- print(msg) # so it appears in Setup.log
- with open(LOG, 'a') as output:
- output.write("%s\n" % (msg,)) # so it appears in our log
- except IOError:
- # Could not write to log
- pass
-
-
-def startPostgres():
- """
- Start postgres via serveradmin
-
- This will block until postgres is up and running
- """
- log("Starting %s via %s" % (POSTGRES_SERVICE_NAME, SERVER_ADMIN))
- ret = subprocess.call([SERVER_ADMIN, "start", POSTGRES_SERVICE_NAME])
- log("serveradmin exited with %d" % (ret,))
-
-def stopPostgres():
- """
- Stop postgres via serveradmin
- """
- log("Stopping %s via %s" % (POSTGRES_SERVICE_NAME, SERVER_ADMIN))
- ret = subprocess.call([SERVER_ADMIN, "stop", POSTGRES_SERVICE_NAME])
- log("serveradmin exited with %d" % (ret,))
-
-
-def dumpOldDatabase(dumpFile):
- """
- Use pg_dump to dump data to dumpFile
- """
-
- cmdArgs = [
- PGDUMP,
- "-h", PGSOCKETDIR,
- "--username=%s" % (USERNAME,),
- "--inserts",
- "--no-privileges",
- "--file=%s" % (dumpFile,),
- DATABASENAME
- ]
- try:
- log("Dumping data to %s" % (dumpFile,))
- log("Executing: %s" % (" ".join(cmdArgs)))
- out = subprocess.check_output(cmdArgs, stderr=subprocess.STDOUT)
- log(out)
- return True
- except subprocess.CalledProcessError, e:
- log(e.output)
- return False
-
-
-def dropOldDatabase():
- """
- Use dropdb to delete the caldav database from the shared postgres server
- """
-
- cmdArgs = [
- DROPDB,
- "-h", PGSOCKETDIR,
- "--username=%s" % (USERNAME,),
- DATABASENAME
- ]
- try:
- log("\nDropping %s database" % (DATABASENAME,))
- log("Executing: %s" % (" ".join(cmdArgs)))
- out = subprocess.check_output(cmdArgs, stderr=subprocess.STDOUT)
- log(out)
- return True
- except subprocess.CalledProcessError, e:
- log(e.output)
- return False
-
-
-def getDefaultCert():
- """
- Ask certadmin for default cert
- @returns: path to default certificate, or empty string if no default
- @rtype: C{str}
- """
- child = subprocess.Popen(
- args=[CERT_ADMIN, "--default-certificate-path"],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- output, error = child.communicate()
- if child.returncode:
- log("Error looking up default certificate (%d): %s" % (child.returncode, error))
- return ""
- else:
- certPath = output.strip()
- log("Default certificate is: %s" % (certPath,))
- return certPath
-
-def updateSettings(settings, otherCert):
- """
- Replace SSL settings based on otherCert path
- """
- basePath = otherCert[:-len("cert.pem")]
- log("Base path is %s" % (basePath,))
-
- log("Setting SSLCertificate to %s" % (otherCert,))
- settings["SSLCertificate"] = otherCert
-
- otherChain = basePath + "chain.pem"
- log("Setting SSLAuthorityChain to %s" % (otherChain,))
- settings["SSLAuthorityChain"] = otherChain
-
- otherKey = basePath + "key.pem"
- log("Setting SSLPrivateKey to %s" % (otherKey,))
- settings["SSLPrivateKey"] = otherKey
-
- settings["EnableSSL"] = True
- settings["RedirectHTTPToHTTPS"] = True
- settings.setdefault("Authentication", {}).setdefault("Basic", {})["Enabled"] = True
-
-def setCert(plistPath, otherCert):
- """
- Replace SSL settings in plist at plistPath based on otherCert path
- """
- log("Reading plist %s" % (plistPath,))
- plist = readPlist(plistPath)
- log("Read in plist %s" % (plistPath,))
-
- updateSettings(plist, otherCert)
-
- log("Writing plist %s" % (plistPath,))
- writePlist(plist, plistPath)
-
-def isSSLEnabled(plistPath):
- """
- Examine plist for EnableSSL
- """
- log("Reading plist %s" % (plistPath,))
- plist = readPlist(plistPath)
- return plist.get("EnableSSL", False)
-
-def main():
- startPostgres()
- if dumpOldDatabase(DATADUMPFILENAME):
- dropOldDatabase()
- stopPostgres()
-
- if not isSSLEnabled(CALDAVD_PLIST):
- defaultCertPath = getDefaultCert()
- log("Default cert path: %s" % (defaultCertPath,))
- if defaultCertPath:
- setCert(CALDAVD_PLIST, defaultCertPath)
-
-
-if __name__ == "__main__":
- main()
Deleted: CalendarServer/trunk/contrib/migration/calendardemotion.py
===================================================================
--- CalendarServer/trunk/contrib/migration/calendardemotion.py 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/contrib/migration/calendardemotion.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -1,38 +0,0 @@
-#!/usr/bin/env python
-#
-# UninstallExtra script for calendar server.
-#
-# Copyright (c) 2011-2013 Apple Inc. All Rights Reserved.
-#
-# IMPORTANT NOTE: This file is licensed only for use on Apple-labeled
-# computers and is subject to the terms and conditions of the Apple
-# Software License Agreement accompanying the package this file is a
-# part of. You may not port this file to another platform without
-# Apple's written consent.
-from __future__ import print_function
-
-import os
-from plistlib import readPlist, writePlist
-
-CALENDAR_SERVER_ROOT = "/Library/Server/Calendar and Contacts"
-DEST_CONFIG_DIR = "%s/Config" % (CALENDAR_SERVER_ROOT,)
-CALDAVD_PLIST = "caldavd.plist"
-
-def main():
-
- plistPath = os.path.join(DEST_CONFIG_DIR, CALDAVD_PLIST)
-
- if os.path.exists(plistPath):
- try:
- # Turn off services
- plistData = readPlist(plistPath)
- plistData["EnableCalDAV"] = False
- plistData["EnableCardDAV"] = False
- writePlist(plistData, plistPath)
-
- except Exception, e:
- print("Unable to disable services in %s: %s" % (plistPath, e))
-
-
-if __name__ == '__main__':
- main()
Deleted: CalendarServer/trunk/contrib/migration/calendarmigrator.py
===================================================================
--- CalendarServer/trunk/contrib/migration/calendarmigrator.py 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/contrib/migration/calendarmigrator.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -1,900 +0,0 @@
-#!/usr/bin/env python
-#
-# MigrationExtra script to maintain the enabled/disabled state of the
-# calendar server.
-#
-# This script examines the launchd preferences from the previous system
-# (also taking into account the overrides.plist) and then invokes serveradmin
-# to start/stop calendar server.
-#
-# The only argument this script currently cares about is --sourceRoot, which
-# should point to the root of the previous system.
-#
-# Copyright (c) 2005-2013 Apple Inc. All Rights Reserved.
-#
-# IMPORTANT NOTE: This file is licensed only for use on Apple-labeled
-# computers and is subject to the terms and conditions of the Apple
-# Software License Agreement accompanying the package this file is a
-# part of. You may not port this file to another platform without
-# Apple's written consent.
-from __future__ import print_function
-from __future__ import with_statement
-
-import datetime
-import grp
-import optparse
-import os
-import pwd
-import shutil
-import subprocess
-import sys
-
-from plistlib import readPlist, readPlistFromString, writePlist
-
-SERVER_APP_ROOT = "/Applications/Server.app/Contents/ServerRoot"
-LOG = "/Library/Logs/Migration/calendarmigrator.log"
-CALDAVD_CONFIG_DIR = "private/etc/caldavd"
-CARDDAVD_CONFIG_DIR = "private/etc/carddavd"
-CALDAVD_PLIST = "caldavd.plist"
-CARDDAVD_PLIST = "carddavd.plist"
-NEW_SERVER_DIR = "Calendar and Contacts"
-NEW_SERVER_ROOT = "/Library/Server/" + NEW_SERVER_DIR
-NEW_CONFIG_DIR = "Library/Server/" + NEW_SERVER_DIR + "/Config"
-LOG_DIR = "var/log/caldavd"
-DITTO = "/usr/bin/ditto"
-RESOURCE_MIGRATION_TRIGGER = "trigger_resource_migration"
-
-# For looking up previous run state
-CALDAV_LAUNCHD_KEY = "org.calendarserver.calendarserver"
-CARDDAV_LAUNCHD_KEY = "org.addressbookserver.addressbookserver"
-LAUNCHD_OVERRIDES = "var/db/launchd.db/com.apple.launchd/overrides.plist"
-LAUNCHD_PREFS_DIR = "System/Library/LaunchDaemons"
-SERVER_ADMIN = "%s/usr/sbin/serveradmin" % (SERVER_APP_ROOT,)
-
-# Processed by mergePlist
-specialKeys = """
-Authentication
-BindHTTPPorts
-BindSSLPorts
-DataRoot
-DirectoryService
-DocumentRoot
-EnableSSL
-HTTPPort
-RedirectHTTPToHTTPS
-SSLAuthorityChain
-SSLCertificate
-SSLPort
-SSLPrivateKey
-""".split()
-
-# Ignored by mergePlist
-ignoredKeys = """
-EnableFindSharedReport
-EnableNotifications
-MaxAddressBookMultigetHrefs
-MaxAddressBookQueryResults
-PythonDirector
-Verbose
-""".split()
-
-
-def main():
-
- optionParser = optparse.OptionParser()
-
- optionParser.add_option('--purge', choices=('0', '1'),
- metavar='[0|1]',
- help='remove old files after migration (IGNORED)')
-
- optionParser.add_option('--sourceRoot', type='string',
- metavar='DIR',
- help='path to the root of the system to migrate')
-
- optionParser.add_option('--sourceType', type='string',
- metavar='[System|TimeMachine]',
- help='migration source type (IGNORED)')
-
- optionParser.add_option('--sourceVersion', type='string',
- metavar='10.X.X',
- help='version number of previous system')
-
- optionParser.add_option('--targetRoot', type='string',
- metavar='DIR',
- help='path to the root of the new system',
- default='/')
-
- optionParser.add_option('--language',
- help='language identifier (IGNORED)',
- default="en")
-
- (options, args) = optionParser.parse_args()
- log("Options: %s" % (options,))
-
- if options.sourceRoot and options.sourceVersion:
-
- if os.path.exists(options.sourceRoot):
-
- enableCalDAV, enableCardDAV = examineRunState(options)
-
- # Pull values out of previous plists
- (
- oldServerRootValue,
- oldCalDocumentRootValue,
- oldCalDataRootValue,
- oldABDocumentRootValue,
- uid,
- gid
- ) = examinePreviousSystem(
- options.sourceRoot,
- options.targetRoot
- )
-
- # Copy data as needed
- (
- newServerRoot,
- newServerRootValue,
- newDataRootValue
- ) = relocateData(
- options.sourceRoot,
- options.targetRoot,
- options.sourceVersion,
- oldServerRootValue,
- oldCalDocumentRootValue,
- oldCalDataRootValue,
- oldABDocumentRootValue,
- uid,
- gid
- )
-
- # Combine old and new plists
- migrateConfiguration(
- options,
- newServerRootValue,
- newDataRootValue,
- enableCalDAV,
- enableCardDAV
- )
-
- # Create log directory
- try:
- logDir = os.path.join(options.targetRoot, LOG_DIR)
- os.mkdir(logDir, 0755)
- except OSError:
- # Already exists
- pass
- # Set ownership
- os.chown(logDir, uid, gid)
-
- # Trigger migration of locations and resources from OD
- triggerResourceMigration(newServerRoot)
-
- # TODO: instead of starting now, leave breadcrumbs for
- # the commonextra to start the service, so that data can
- # be dumped from the old Postgres to a file which will
- # be executed by calendar server when it next starts up.
-
- # setRunState(options, enableCalDAV, enableCardDAV)
-
- else:
- log("ERROR: --sourceRoot and --sourceVersion must be specified")
- sys.exit(1)
-
-
-def examineRunState(options):
- """
- Try to determine whether the CalDAV and CardDAV services were running in
- previous system.
-
- @return: a tuple of booleans: whether CalDAV was enabled, and whether
- CardDAV was enabled
- """
-
- enableCalDAV = None
- enableCardDAV = None
-
- try:
- disabled = isServiceDisabled(options.sourceRoot, CALDAV_LAUNCHD_KEY)
- enableCalDAV = not disabled
- log("Calendar service '%s' was previously %s" %
- (CALDAV_LAUNCHD_KEY, "disabled" if disabled else "enabled"))
- except ServiceStateError, e:
- log("Couldn't determine previous state of calendar service '%s': %s" %
- (CALDAV_LAUNCHD_KEY, e))
-
- try:
- disabled = isServiceDisabled(options.sourceRoot, CARDDAV_LAUNCHD_KEY)
- enableCardDAV = not disabled
- log("Addressbook service '%s' was previously %s" %
- (CARDDAV_LAUNCHD_KEY, "disabled" if disabled else "enabled"))
- except ServiceStateError, e:
- log("Couldn't determine previous state of addressbook service '%s': %s" %
- (CARDDAV_LAUNCHD_KEY, e))
-
- if enableCalDAV:
- # Check previous plist in case previous system was Lion, since there
- # is now only one launchd key for both services
- oldCalDAVPlistPath = os.path.join(options.sourceRoot,
- CALDAVD_CONFIG_DIR, CALDAVD_PLIST)
- if os.path.exists(oldCalDAVPlistPath):
- log("Examining previous caldavd.plist for EnableCalDAV and EnableCardDAV: %s" % (oldCalDAVPlistPath,))
- oldCalDAVDPlist = readPlist(oldCalDAVPlistPath)
- if "EnableCalDAV" in oldCalDAVDPlist:
- enableCalDAV = oldCalDAVDPlist["EnableCalDAV"]
- log("Based on caldavd.plist, setting EnableCalDAV to %s" % (enableCalDAV,))
- if "EnableCardDAV" in oldCalDAVDPlist:
- enableCardDAV = oldCalDAVDPlist["EnableCardDAV"]
- log("Based on caldavd.plist, setting EnableCardDAV to %s" % (enableCardDAV,))
-
- # A value of None means we weren't able to determine, so default to off
- if enableCalDAV is None:
- enableCalDAV = False
- if enableCardDAV is None:
- enableCardDAV = False
-
- return (enableCalDAV, enableCardDAV)
-
-
-def setRunState(options, enableCalDAV, enableCardDAV):
- """
- Use serveradmin to launch the service if needed.
- """
-
- if enableCalDAV or enableCardDAV:
- serviceName = "calendar" if enableCalDAV else "addressbook"
- log("Starting service via serveradmin start %s" % (serviceName,))
- ret = subprocess.call([SERVER_ADMIN, "start", serviceName])
- log("serveradmin exited with %d" % (ret,))
-
-
-def isServiceDisabled(source, service, launchdOverrides=LAUNCHD_OVERRIDES,
- launchdPrefsDir=LAUNCHD_PREFS_DIR):
- """
- Returns whether or not a service is disabled
-
- @param source: System root to examine
- @param service: launchd key representing service
- @return: True if service is disabled, False if enabled
- """
-
- overridesPath = os.path.join(source, launchdOverrides)
- if os.path.isfile(overridesPath):
- try:
- overrides = readPlist(overridesPath)
- except Exception, e:
- raise ServiceStateError("Could not parse %s : %s" %
- (overridesPath, str(e)))
-
- try:
- return overrides[service]['Disabled']
- except KeyError:
- # Key is not in the overrides.plist, continue on
- pass
-
- prefsPath = os.path.join(source, launchdPrefsDir, "%s.plist" % service)
- if os.path.isfile(prefsPath):
- try:
- prefs = readPlist(prefsPath)
- except Exception, e:
- raise ServiceStateError("Could not parse %s : %s" %
- (prefsPath, str(e)))
- try:
- return prefs['Disabled']
- except KeyError:
- return False
-
- raise ServiceStateError("Neither %s nor %s exist" %
- (overridesPath, prefsPath))
-
-
-class ServiceStateError(Exception):
- """
- Could not determine service state
- """
-
-
-
-def migrateConfiguration(options, newServerRootValue, newDataRootValue, enableCalDAV, enableCardDAV):
- """
- Copy files/directories/symlinks from previous system's /etc/caldavd
- and /etc/carddavd
-
- Skips anything ending in ".default".
- Regular files overwrite copies in new system.
- Directories and symlinks only copied over if they don't overwrite anything.
- """
-
- newConfigDir = os.path.join(options.targetRoot, NEW_CONFIG_DIR)
- newConfigFile = os.path.join(newConfigDir, CALDAVD_PLIST)
-
- # Create config directory if it doesn't exist
- if not os.path.exists(newConfigDir):
- os.mkdir(newConfigDir)
-
- defaultConfig = os.path.join(SERVER_APP_ROOT, CALDAVD_CONFIG_DIR, CALDAVD_PLIST)
- if os.path.exists(defaultConfig) and not os.path.exists(newConfigFile):
- log("Copying default config file %s to %s" % (defaultConfig, newConfigFile))
- shutil.copy2(defaultConfig, newConfigFile)
-
- for configDir in (NEW_CONFIG_DIR, CALDAVD_CONFIG_DIR, CARDDAVD_CONFIG_DIR):
-
- oldConfigDir = os.path.join(options.sourceRoot, configDir)
- if not os.path.exists(oldConfigDir):
- log("Old configuration directory does not exist: %s" % (oldConfigDir,))
- continue
-
- log("Copying configuration files from %s to %s" % (oldConfigDir, newConfigDir))
-
- for name in os.listdir(oldConfigDir):
-
- if not (name.endswith(".default") or name in (CALDAVD_PLIST, CARDDAVD_PLIST)):
-
- oldPath = os.path.join(oldConfigDir, name)
- newPath = os.path.join(newConfigDir, name)
-
- if os.path.islink(oldPath) and not os.path.exists(newPath):
- # Recreate the symlink if it won't overwrite an existing file
- link = os.readlink(oldPath)
- log("Symlinking %s to %s" % (newPath, link))
- os.symlink(link, newPath)
-
- elif os.path.isfile(oldPath):
- # Copy the file over, overwriting copy in newConfigDir
- log("Copying file %s to %s" % (oldPath, newConfigDir))
- shutil.copy2(oldPath, newConfigDir)
-
- elif os.path.isdir(oldPath) and not os.path.exists(newPath):
- # Copy the dir over, but only if new one doesn't exist
- log("Copying directory %s to %s" % (oldPath, newPath))
- shutil.copytree(oldPath, newPath, symlinks=True)
-
-
- # Migrate certain settings from the old plists to new:
-
- oldCalDAVPlistPath = os.path.join(options.sourceRoot, CALDAVD_CONFIG_DIR,
- CALDAVD_PLIST)
- if os.path.exists(oldCalDAVPlistPath):
- oldCalDAVDPlist = readPlist(oldCalDAVPlistPath)
- else:
- oldCalDAVDPlist = { }
-
- oldCardDAVDPlistPath = os.path.join(options.sourceRoot, CARDDAVD_CONFIG_DIR,
- CARDDAVD_PLIST)
- if os.path.exists(oldCardDAVDPlistPath):
- oldCardDAVDPlist = readPlist(oldCardDAVDPlistPath)
- else:
- oldCardDAVDPlist = { }
-
- if os.path.exists(newConfigFile):
- newCalDAVDPlist = readPlist(newConfigFile)
- else:
- newCalDAVDPlist = { }
-
- log("Processing %s and %s" % (oldCalDAVPlistPath, oldCardDAVDPlistPath))
- adminChanges = mergePlist(oldCalDAVDPlist, oldCardDAVDPlist, newCalDAVDPlist)
-
- newCalDAVDPlist["ServerRoot"] = newServerRootValue
- newCalDAVDPlist["DocumentRoot"] = "Documents"
- newCalDAVDPlist["DataRoot"] = newDataRootValue
-
- newCalDAVDPlist["EnableCalDAV"] = enableCalDAV
- newCalDAVDPlist["EnableCardDAV"] = enableCardDAV
-
- log("Writing %s" % (newConfigFile,))
- writePlist(newCalDAVDPlist, newConfigFile)
-
- for key, value in adminChanges:
- log("Setting %s to %s via serveradmin..." % (key, value))
- ret = subprocess.call([SERVER_ADMIN, "settings", "calendar:%s=%s" % (key, value)])
- log("serveradmin exited with %d" % (ret,))
-
-
-
-def mergePlist(caldav, carddav, combined):
-
- adminChanges = []
-
- # Copy all non-ignored keys
- for key in carddav:
- if key not in ignoredKeys and key not in specialKeys:
- combined[key] = carddav[key]
- for key in caldav:
- if key not in ignoredKeys and key not in specialKeys:
- combined[key] = caldav[key]
-
- # Copy all "Authentication" sub-keys
- if "Authentication" in caldav:
- if "Authentication" not in combined:
- combined["Authentication"] = { }
- for key in caldav["Authentication"]:
- combined["Authentication"][key] = caldav["Authentication"][key]
-
- # Reset the wiki settings since URL is only used wieh LionCompatibility
- combined["Authentication"]["Wiki"] = { "Enabled" : True }
-
- # Strip out any unknown params from the DirectoryService:
- if "DirectoryService" in caldav:
- combined["DirectoryService"] = caldav["DirectoryService"]
- for key in combined["DirectoryService"]["params"].keys():
- if key in ("requireComputerRecord",):
- del combined["DirectoryService"]["params"][key]
-
- # Disable XMPPNotifier now that we're directly talking to APNS
- try:
- XMPPNotifier = caldav["Notifications"]["Services"]["XMPPNotifier"]
- if XMPPNotifier["Enabled"]:
- XMPPNotifier["Enabled"] = False
- except KeyError:
- pass
-
- # If XMPP was also previously being routed to APNS, enable APNS
- EnableAPNS = False
- try:
- if caldav["Notifications"]["Services"]["XMPPNotifier"]["CalDAV"]["APSBundleID"]:
- EnableAPNS = True
- except KeyError:
- pass
- try:
- if caldav["Notifications"]["Services"]["XMPPNotifier"]["CardDAV"]["APSBundleID"]:
- EnableAPNS = True
- except KeyError:
- pass
- if EnableAPNS:
- adminChanges.append(["EnableAPNS", "yes"])
-
- # Merge ports
- if not caldav.get("HTTPPort", 0):
- caldav["HTTPPort"] = 8008
- if not carddav.get("HTTPPort", 0):
- carddav["HTTPPort"] = 8800
- if not caldav.get("SSLPort", 0):
- caldav["SSLPort"] = 8443
- if not carddav.get("SSLPort", 0):
- carddav["SSLPort"] = 8843
-
- for portType in ["HTTPPort", "SSLPort"]:
- bindPorts = list(set(caldav.get("Bind%ss" % (portType,), [])).union(set(carddav.get("Bind%ss" % (portType,), []))))
- for prev in (carddav, caldav):
- port = prev.get(portType, 0)
- if port and port not in bindPorts:
- bindPorts.append(port)
- bindPorts.sort()
- combined["Bind%ss" % (portType,)] = bindPorts
-
- combined["HTTPPort"] = caldav["HTTPPort"]
- combined["SSLPort"] = caldav["SSLPort"]
-
- # Was SSL enabled?
- sslAuthorityChain = ""
- sslCertificate = ""
- sslPrivateKey = ""
- enableSSL = False
- for prev in (carddav, caldav):
- if (prev["SSLPort"] and prev.get("SSLCertificate", "")):
- sslAuthorityChain = prev.get("SSLAuthorityChain", "")
- sslCertificate = prev.get("SSLCertificate", "")
- sslPrivateKey = prev.get("SSLPrivateKey", "")
- enableSSL = True
-
- combined["SSLAuthorityChain"] = sslAuthorityChain
- combined["SSLCertificate"] = sslCertificate
- combined["SSLPrivateKey"] = sslPrivateKey
- combined["EnableSSL"] = enableSSL
-
- # If SSL is enabled, redirect HTTP to HTTPS.
- combined["RedirectHTTPToHTTPS"] = enableSSL
-
- # New DBType value indicating we launch our own Postgres
- combined["DBType"] = ""
-
- # No DSN value since we launch our own Postgres
- combined["DSN"] = ""
-
- # Path to SQL file to import previous data from
- combined["DBImportFile"] = "/Library/Server/Calendar and Contacts/DataDump.sql"
-
- # ConfigRoot is now always "Config"
- combined["ConfigRoot"] = "Config"
-
- # Remove RunRoot and PIDFile keys so they use the new defaults
- try:
- del combined["RunRoot"]
- except:
- pass
- try:
- del combined["PIDFile"]
- except:
- pass
-
- return adminChanges
-
-
-def log(msg):
- try:
- timestamp = datetime.datetime.now().strftime("%b %d %H:%M:%S")
- msg = "calendarmigrator: %s %s" % (timestamp, msg)
- print(msg) # so it appears in Setup.log
- with open(LOG, 'a') as output:
- output.write("%s\n" % (msg,)) # so it appears in our log
- except IOError:
- # Could not write to log
- pass
-
-def examinePreviousSystem(sourceRoot, targetRoot, diskAccessor=None):
- """
- Examines the old caldavd.plist and carddavd.plist to see where data
- lives in the previous system.
- """
-
- if diskAccessor is None:
- diskAccessor = DiskAccessor()
-
- oldServerRootValue = None
- oldCalDocumentRootValue = None
- oldCalDataRootValue = None
- oldABDocumentRootValue = None
-
- uid = pwd.getpwnam("calendar").pw_uid
- gid = grp.getgrnam("calendar").gr_gid
-
- # Try and read old caldavd.plist
- oldCalConfigDir = os.path.join(sourceRoot, CALDAVD_CONFIG_DIR)
- oldCalPlistPath = os.path.join(oldCalConfigDir, CALDAVD_PLIST)
- if diskAccessor.exists(oldCalPlistPath):
- contents = diskAccessor.readFile(oldCalPlistPath)
- oldCalPlist = readPlistFromString(contents)
- log("Found previous caldavd plist at %s" % (oldCalPlistPath,))
-
- oldServerRootValue = oldCalPlist.get("ServerRoot", None)
- oldCalDocumentRootValue = oldCalPlist.get("DocumentRoot", None)
- oldCalDataRootValue = oldCalPlist.get("DataRoot", None)
-
- else:
- log("Can't find previous calendar plist at %s" % (oldCalPlistPath,))
- oldCalPlist = None
-
- # Try and read old carddavd.plist
- oldABConfigDir = os.path.join(sourceRoot, CARDDAVD_CONFIG_DIR)
- oldABPlistPath = os.path.join(oldABConfigDir, CARDDAVD_PLIST)
- if diskAccessor.exists(oldABPlistPath):
- contents = diskAccessor.readFile(oldABPlistPath)
- oldABPlist = readPlistFromString(contents)
- log("Found previous carddavd plist at %s" % (oldABPlistPath,))
-
- oldABDocumentRootValue = oldABPlist.get("DocumentRoot", None)
- else:
- log("Can't find previous carddavd plist at %s" % (oldABPlistPath,))
- oldABPlist = None
-
- return (
- oldServerRootValue,
- oldCalDocumentRootValue,
- oldCalDataRootValue,
- oldABDocumentRootValue,
- uid,
- gid
- )
-
-
-def relocateData(sourceRoot, targetRoot, sourceVersion, oldServerRootValue,
- oldCalDocumentRootValue, oldCalDataRootValue, oldABDocumentRootValue,
- uid, gid, diskAccessor=None):
- """
- Copy data from sourceRoot to targetRoot, except when data is on another
- volume in which case we just refer to it there.
- """
-
- if diskAccessor is None:
- diskAccessor = DiskAccessor()
-
- log("RelocateData: sourceRoot=%s, targetRoot=%s, oldServerRootValue=%s, oldCalDocumentRootValue=%s, oldCalDataRootValue=%s, oldABDocumentRootValue=%s, uid=%d, gid=%d" % (sourceRoot, targetRoot, oldServerRootValue, oldCalDocumentRootValue, oldCalDataRootValue, oldABDocumentRootValue, uid, gid))
-
- newServerRootValue = "/Library/Server/Calendar and Contacts"
- newServerRoot = absolutePathWithRoot(targetRoot, newServerRootValue)
-
- if sourceVersion < "10.7":
- oldCalDocumentRootValueProcessed = oldCalDocumentRootValue
- oldCalDataRootValueProcessed = oldCalDataRootValue
-
- else:
- # If there was an old ServerRoot value, process DocumentRoot and
- # DataRoot because those could be relative to ServerRoot
-
- if sourceVersion < "10.8":
- # DocumentRoot and DataRoot are both relative to ServerRoot
- oldCalDocumentRootValueProcessed = os.path.join(oldServerRootValue,
- oldCalDocumentRootValue)
- oldCalDataRootValueProcessed = os.path.join(oldServerRootValue,
- oldCalDataRootValue)
- else:
- # DocumentRoot is relative to DataRoot, DataRoot is relative to ServerRoot
- oldCalDataRootValueProcessed = os.path.join(oldServerRootValue,
- oldCalDataRootValue)
- oldCalDocumentRootValueProcessed = os.path.join(oldCalDataRootValueProcessed,
- oldCalDocumentRootValue)
-
-
- # Set default values for these, possibly overridden below:
- newDataRootValue = "Data"
- newDataRoot = absolutePathWithRoot(
- targetRoot,
- os.path.join(newServerRootValue, newDataRootValue)
- )
- newDocumentRootValue = "Documents"
- newDocumentRoot = os.path.join(newDataRoot, newDocumentRootValue)
-
- if sourceVersion < "10.7":
- # Before 10.7 there was no ServerRoot; DocumentRoot and DataRoot were separate.
- # Reconfigure so DocumentRoot is under DataRoot is under ServerRoot. DataRoot
- # will be /Library/Server/Calendar and Contacts/Data unless old DocumentRoot was on
- # an external volume, in which case that becomes the new DataRoot and DocumentRoot
- # moves under DataRoot.
- # /Library/Server/Calendar and Contacts will be new ServerRoot no matter what.
-
- if oldCalDocumentRootValueProcessed:
- if oldCalDocumentRootValueProcessed.startswith("/Volumes/"): # external volume
- # The old external calendar DocumentRoot becomes the new DataRoot
- newDataRoot = newDataRootValue = os.path.join(os.path.dirname(oldCalDocumentRootValue.rstrip("/")), "Calendar and Contacts Data")
- newDocumentRoot = os.path.join(newDataRoot, newDocumentRootValue)
- # Move aside whatever is there
- if diskAccessor.exists(newDataRoot):
- renameTo = nextAvailable(newDataRoot, "bak", diskAccessor=diskAccessor)
- diskAccessor.rename(newDataRoot, renameTo)
-
- if diskAccessor.exists(absolutePathWithRoot(sourceRoot, oldCalDataRootValueProcessed)):
- diskAccessor.ditto(
- absolutePathWithRoot(sourceRoot, oldCalDataRootValueProcessed),
- newDataRoot
- )
- else:
- diskAccessor.mkdir(newDataRoot)
-
- # Move old DocumentRoot under new DataRoot
- diskAccessor.rename(oldCalDocumentRootValue, newDocumentRoot)
- diskAccessor.chown(newDataRoot, uid, gid, recursive=True)
-
- else: # The old calendar DocumentRoot is not external
- if oldCalDataRootValueProcessed:
- if diskAccessor.exists(absolutePathWithRoot(sourceRoot,
- oldCalDataRootValueProcessed)):
- diskAccessor.ditto(
- absolutePathWithRoot(sourceRoot, oldCalDataRootValueProcessed),
- newDataRoot
- )
- if diskAccessor.exists(absolutePathWithRoot(sourceRoot,
- oldCalDocumentRootValueProcessed)):
- diskAccessor.ditto(
- absolutePathWithRoot(sourceRoot, oldCalDocumentRootValueProcessed),
- newDocumentRoot
- )
-
- # Old AddressBook DocumentRoot
- if oldABDocumentRootValue:
- newAddressBooks = os.path.join(newDocumentRoot, "addressbooks")
- if oldABDocumentRootValue.startswith("/Volumes/"): # external volume
- diskAccessor.ditto(
- os.path.join(oldABDocumentRootValue, "addressbooks"),
- newAddressBooks
- )
- elif diskAccessor.exists(
- absolutePathWithRoot(sourceRoot, oldABDocumentRootValue)
- ):
- diskAccessor.ditto(
- absolutePathWithRoot(
- sourceRoot,
- os.path.join(oldABDocumentRootValue, "addressbooks")
- ),
- os.path.join(newDocumentRoot, "addressbooks")
- )
-
-
- elif sourceVersion < "10.8":
- # Before 10.8, DocumentRoot and DataRoot were relative to ServerRoot
-
- if oldServerRootValue:
- if oldServerRootValue.rstrip("/").startswith("/Volumes/"): # external volume
- log("Using external calendar server root: %s" % (oldServerRootValue,))
- # ServerRoot needs to be /Library/Server/Calendar and Contacts
- # Since DocumentRoot is now relative to DataRoot, move DocumentRoot into DataRoot
- newDataRoot = newDataRootValue = os.path.join(oldServerRootValue, "Data")
- if not diskAccessor.exists(newDataRoot):
- diskAccessor.mkdir(newDataRoot)
- newDocumentRoot = os.path.join(newDataRootValue, "Documents")
- if not diskAccessor.exists(newDocumentRoot):
- if diskAccessor.exists(os.path.join(oldServerRootValue, "Documents")):
- diskAccessor.rename(os.path.join(oldServerRootValue, "Documents"),
- newDocumentRoot)
- else:
- diskAccessor.mkdir(newDocumentRoot)
- elif diskAccessor.exists(absolutePathWithRoot(sourceRoot, oldServerRootValue)):
- log("Copying calendar server root: %s" % (newServerRoot,))
- diskAccessor.ditto(
- absolutePathWithRoot(sourceRoot, oldServerRootValue),
- newServerRoot
- )
- newDataRoot = os.path.join(newServerRoot, "Data")
- if not diskAccessor.exists(newDataRoot):
- diskAccessor.mkdir(newDataRoot)
- newDocumentRoot = os.path.join(newDataRoot, "Documents")
- if not diskAccessor.exists(newDocumentRoot):
- if diskAccessor.exists(os.path.join(newServerRoot, "Documents")):
- log("Moving Documents into Data root: %s" % (newDataRoot,))
- diskAccessor.rename(os.path.join(newServerRoot, "Documents"),
- newDocumentRoot)
- else:
- diskAccessor.mkdir(newDocumentRoot)
- else:
- if not diskAccessor.exists(newServerRoot):
- log("Creating new calendar server root: %s" % (newServerRoot,))
- diskAccessor.mkdir(newServerRoot)
- newDataRoot = os.path.join(newServerRoot, "Data")
- if not diskAccessor.exists(newDataRoot):
- log("Creating new data root: %s" % (newDataRoot,))
- diskAccessor.mkdir(newDataRoot)
- newDocumentRoot = os.path.join(newDataRoot, "Documents")
- if not diskAccessor.exists(newDocumentRoot):
- log("Creating new document root: %s" % (newDocumentRoot,))
- diskAccessor.mkdir(newDocumentRoot)
-
-
- else: # 10.8 -> 10.8
-
- if oldServerRootValue:
- if oldServerRootValue.rstrip("/").startswith("/Volumes/"): # external volume
- log("Using external calendar server root: %s" % (oldServerRootValue,))
- elif diskAccessor.exists(absolutePathWithRoot(sourceRoot, oldServerRootValue)):
- log("Copying calendar server root: %s" % (newServerRoot,))
- diskAccessor.ditto(
- absolutePathWithRoot(sourceRoot, oldServerRootValue),
- newServerRoot
- )
- else:
- log("Creating new calendar server root: %s" % (newServerRoot,))
- diskAccessor.mkdir(newServerRoot)
- newDataRoot = os.path.join(newServerRoot, "Data")
- diskAccessor.mkdir(newDataRoot)
- newDocumentRoot = os.path.join(newDataRoot, "Documents")
- diskAccessor.mkdir(newDocumentRoot)
-
- if not diskAccessor.exists(newServerRoot):
- diskAccessor.mkdir(newServerRoot)
- diskAccessor.chown(newServerRoot, uid, gid, recursive=True)
-
- newServerRootValue, newDataRootValue = relativize(newServerRootValue,
- newDataRootValue)
- newDataRootValue, newDocumentRootValue = relativize(newDataRootValue,
- newDocumentRootValue)
-
-
- return (
- newServerRoot,
- newServerRootValue,
- newDataRootValue
- )
-
-
-def triggerResourceMigration(newServerRoot):
- """
- Leave a file in the server root to act as a signal that the server
- should migrate locations and resources from OD when it starts up.
- """
- triggerPath = os.path.join(newServerRoot, RESOURCE_MIGRATION_TRIGGER)
- if not os.path.exists(newServerRoot):
- log("New server root directory doesn't exist: %s" % (newServerRoot,))
- return
-
- if not os.path.exists(triggerPath):
- # Create an empty trigger file
- log("Creating resource migration trigger file: %s" % (triggerPath,))
- open(triggerPath, "w").close()
-
-
-def relativize(parent, child):
- """
- If child is really a child of parent, make child relative to parent.
- """
- if child.startswith(parent):
- parent = parent.rstrip("/")
- child = child[len(parent):].strip("/")
- return parent.rstrip("/"), child.rstrip("/")
-
-
-def absolutePathWithRoot(root, path):
- """
- Combine root and path as long as path does not start with /Volumes/
- """
- if path.startswith("/Volumes/"):
- return path
- else:
- path = path.strip("/")
- return os.path.join(root, path)
-
-
-def nextAvailable(path, ext, diskAccessor=None):
- """
- If path.ext doesn't exist, return path.ext. Otherwise return the first path name
- following the path.N.ext pattern that doesn't exist, where N starts at 1
- and increments until a non-existent path name is determined.
-
- @param path: path to examine
- @type path: C{str}
- @param ext: filename extension to append (don't include ".")
- @type ext: C{str}
- @returns: non-existent path name
- @rtype: C{str}
- """
-
- if diskAccessor is None:
- diskAccessor = DiskAccessor()
-
- newPath = "%s.%s" % (path, ext)
- if not diskAccessor.exists(newPath):
- return newPath
-
- i = 1
- while(True):
- newPath = "%s.%d.%s" % (path, i, ext)
- if not diskAccessor.exists(newPath):
- return newPath
- i += 1
-
-
-class DiskAccessor(object):
- """
- A wrapper around various disk access methods so that unit tests can easily
- replace these with a stub that doesn't actually require disk access.
- """
-
- def exists(self, path):
- return os.path.exists(path)
-
- def readFile(self, path):
- input = file(path)
- contents = input.read()
- input.close()
- return contents
-
- def mkdir(self, path):
- if not self.exists(path):
- return os.mkdir(path)
- else:
- return
-
- def rename(self, before, after):
- log("Renaming: %s to %s" % (before, after))
- try:
- return os.rename(before, after)
- except OSError:
- # Can't rename because it's cross-volume; must copy/delete
- self.ditto(before, after)
- return os.remove(before)
-
- def isfile(self, path):
- return os.path.isfile(path)
-
- def symlink(self, orig, link):
- return os.symlink(orig, link)
-
- def chown(self, path, uid, gid, recursive=False):
- os.chown(path, uid, gid)
- if recursive:
- for root, dirs, files in os.walk(path, followlinks=True):
- for name in dirs:
- os.chown(os.path.join(root, name), uid, gid)
- for name in files:
- os.chown(os.path.join(root, name), uid, gid)
-
-
- def walk(self, path, followlinks=True):
- return os.walk(path, followlinks=followlinks)
-
- def listdir(self, path):
- return list(os.listdir(path))
-
- def ditto(self, src, dest):
- log("Copying with ditto: %s to %s" % (src, dest))
- return subprocess.call([DITTO, src, dest])
-
-
-if __name__ == '__main__':
- main()
Deleted: CalendarServer/trunk/contrib/migration/calendarpromotion.py
===================================================================
--- CalendarServer/trunk/contrib/migration/calendarpromotion.py 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/contrib/migration/calendarpromotion.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -1,102 +0,0 @@
-#!/usr/bin/env python
-#
-# PromotionExtra script for calendar server.
-#
-# Copyright (c) 2011-2013 Apple Inc. All Rights Reserved.
-#
-# IMPORTANT NOTE: This file is licensed only for use on Apple-labeled
-# computers and is subject to the terms and conditions of the Apple
-# Software License Agreement accompanying the package this file is a
-# part of. You may not port this file to another platform without
-# Apple's written consent.
-from __future__ import print_function
-
-import os
-import shutil
-from pwd import getpwnam
-from grp import getgrnam
-from plistlib import readPlist, writePlist
-
-SRC_CONFIG_DIR = "/Applications/Server.app/Contents/ServerRoot/private/etc/caldavd"
-CALENDAR_SERVER_ROOT = "/Library/Server/Calendar and Contacts"
-DEST_CONFIG_DIR = "%s/Config" % (CALENDAR_SERVER_ROOT,)
-DEST_DATA_DIR = "%s/Data" % (CALENDAR_SERVER_ROOT,)
-CALDAVD_PLIST = "caldavd.plist"
-USER_NAME = "calendar"
-GROUP_NAME = "calendar"
-LOG_DIR = "/var/log/caldavd"
-RUN_DIR = "/var/run/caldavd"
-
-
-def updatePlist(plistData):
- """
- Update the passed-in plist data with new values for disabling the XMPPNotifier,
- to set DBType to empty string indicating we'll be starting our own Postgres server,
- and to specify the new location for ConfigRoot ("Config" directory beneath ServerRoot).
-
- @param plistData: the plist data to update in place
- @type plistData: C{dict}
- """
- try:
- if plistData["Notifications"]["Services"]["XMPPNotifier"]["Enabled"]:
- plistData["Notifications"]["Services"]["XMPPNotifier"]["Enabled"] = False
- except KeyError:
- pass
- plistData["DBType"] = ""
- plistData["DSN"] = ""
- plistData["ConfigRoot"] = "Config"
- plistData["DBImportFile"] = "/Library/Server/Calendar and Contacts/DataDump.sql"
- # Remove RunRoot and PIDFile keys so they use the new defaults
- try:
- del plistData["RunRoot"]
- except:
- pass
- try:
- del plistData["PIDFile"]
- except:
- pass
-
-
-
-def main():
-
- for dirName in (
- CALENDAR_SERVER_ROOT,
- DEST_CONFIG_DIR,
- DEST_DATA_DIR,
- LOG_DIR,
- RUN_DIR
- ):
- try:
- os.mkdir(dirName)
- except OSError:
- # Already exists
- pass
-
- try:
- uid = getpwnam(USER_NAME).pw_uid
- gid = getgrnam(GROUP_NAME).gr_gid
- os.chown(dirName, uid, gid)
- except Exception, e:
- print("Unable to chown %s: %s" % (dirName, e))
-
-
- plistPath = os.path.join(DEST_CONFIG_DIR, CALDAVD_PLIST)
-
- if os.path.exists(plistPath):
- try:
- plistData = readPlist(plistPath)
- updatePlist(plistData)
- writePlist(plistData, plistPath)
-
- except Exception, e:
- print("Unable to disable update values in %s: %s" % (plistPath, e))
-
- else:
- # Copy configuration
- srcPlistPath = os.path.join(SRC_CONFIG_DIR, CALDAVD_PLIST)
- shutil.copy(srcPlistPath, DEST_CONFIG_DIR)
-
-
-if __name__ == '__main__':
- main()
Added: CalendarServer/trunk/doc/calendarserver_config.8
===================================================================
--- CalendarServer/trunk/doc/calendarserver_config.8 (rev 0)
+++ CalendarServer/trunk/doc/calendarserver_config.8 2013-03-14 21:14:13 UTC (rev 10924)
@@ -0,0 +1,59 @@
+.\"
+.\" Copyright (c) 2006-2013 Apple 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.
+.\"
+.\" The following requests are required for all man pages.
+.Dd January 14, 2013
+.Dt CALENDARSERVER_CONFIG 8
+.Os
+.Sh NAME
+.Nm calendarserver_config
+.Nd Calendar Server Configuration Utility
+.Sh SYNOPSIS
+.Nm
+.Op Fl -config Ar file
+.Op key ...
+.Sh DESCRIPTION
+.Nm
+stores and retrieves configuration values for calendar server. It's primary
+purpose is to carry out operations on behalf of the Apple OS X Server
+administration application; in this mode of operation is reads stdin for
+keys provided in plist format, and writes results to stdout, also in plist
+form. For interactive use, you can list one or more keys as command line
+arguments.
+.Pp
+.Sh OPTIONS
+.Bl -tag -width flag
+.It Fl h, -help
+Displays usage information
+.It Fl f, -config Ar FILE
+Use the Calendar Server configuration specified in the given file.
+Defaults to /Applications/Server.app/Contents/ServerRoot/private/etc/caldavd/caldavd-apple.plist on Apple servers, /etc/caldavd/caldavd.plist on other servers.
+.El
+.Sh EXAMPLES
+Retrieve the value for EnableCalDAV
+.Pp
+.Dl "calendarserver_config EnableCalDAV"
+.Pp
+Set EnableCalDAV to True
+.Pp
+.Dl "calendarserver_config EnableCalDAV=True"
+.Pp
+.Sh FILES
+.Bl -tag -width flag
+.It /Applications/Server.app/Contents/ServerRoot/private/etc/caldavd/caldavd-apple.plist
+The static Calendar Server configuration file (not to be edited).
+.It /Library/Server/Calendar and Contacts/Config/caldavd-system.plist
+The configuration file which stores local overriding values (which calendarserver_config modifies).
+.El
Modified: CalendarServer/trunk/setup.py
===================================================================
--- CalendarServer/trunk/setup.py 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/setup.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -132,7 +132,7 @@
"bin/calendarserver_backup",
"bin/calendarserver_bootstrap_database",
"bin/calendarserver_command_gateway",
- #"bin/calendarserver_config", # Used by run script.
+ "bin/calendarserver_config",
#"bin/calendarserver_dbinspect",
#"bin/calendarserver_dkimtool",
"bin/calendarserver_export",
Modified: CalendarServer/trunk/support/Makefile.Apple
===================================================================
--- CalendarServer/trunk/support/Makefile.Apple 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/support/Makefile.Apple 2013-03-14 21:14:13 UTC (rev 10924)
@@ -85,7 +85,7 @@
$(_v) cd $(BuildDirectory)/pycrypto-2.5 && $(Environment) $(PYTHON) setup.py install $(PY_INSTALL_FLAGS)
$(_v) for so in $$(find "$(DSTROOT)$(PY_HOME)/lib" -type f -name '*.so'); do $(STRIP) -Sx "$${so}"; done
$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SIPP)$(ETCDIR)$(CALDAVDSUBDIR)"
- $(_v) $(INSTALL_FILE) "$(Sources)/conf/caldavd-apple.plist" "$(DSTROOT)$(SIPP)$(ETCDIR)$(CALDAVDSUBDIR)/caldavd.plist"
+ $(_v) $(INSTALL_FILE) "$(Sources)/conf/caldavd-apple.plist" "$(DSTROOT)$(SIPP)$(ETCDIR)$(CALDAVDSUBDIR)/caldavd-apple.plist"
$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SIPP)$(ETCDIR)$(WEBAPPSSUBDIR)"
$(_v) $(INSTALL_FILE) "$(Sources)/contrib/migration/com.apple.webapp.contacts.plist" "$(DSTROOT)$(SIPP)$(ETCDIR)$(WEBAPPSSUBDIR)/com.apple.webapp.contacts.plist"
$(_v) $(INSTALL_FILE) "$(Sources)/contrib/migration/com.apple.webapp.contactsssl.plist" "$(DSTROOT)$(SIPP)$(ETCDIR)$(WEBAPPSSUBDIR)/com.apple.webapp.contactsssl.plist"
@@ -112,22 +112,6 @@
$(_v) $(INSTALL_DIRECTORY) -o "$(CS_USER)" -g "$(CS_GROUP)" -m 0755 "$(DSTROOT)$(VARDIR)/log$(CALDAVDSUBDIR)"
$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SIPP)$(NSLIBRARYDIR)/LaunchDaemons"
$(_v) $(INSTALL_FILE) "$(Sources)/contrib/launchd/calendarserver.plist" "$(DSTROOT)$(SIPP)$(NSLIBRARYDIR)/LaunchDaemons/org.calendarserver.calendarserver.plist"
- @echo "Installing migration extras script..."
- $(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SERVERSETUP)/MigrationExtras"
- $(_v) $(INSTALL_FILE) "$(Sources)/contrib/migration/calendarmigrator.py" "$(DSTROOT)$(SERVERSETUP)/MigrationExtras/70_calendarmigrator.py"
- $(_v) chmod ugo+x "$(DSTROOT)$(SERVERSETUP)/MigrationExtras/70_calendarmigrator.py"
- @echo "Installing common extras script..."
- $(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SERVERSETUP)/CommonExtras"
- $(_v) $(INSTALL_FILE) "$(Sources)/contrib/migration/calendarcommonextra.py" "$(DSTROOT)$(SERVERSETUP)/CommonExtras/70_calendarcommonextra.py"
- $(_v) chmod ugo+x "$(DSTROOT)$(SERVERSETUP)/CommonExtras/70_calendarcommonextra.py"
- @echo "Installing server promotion extras script..."
- $(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SERVERSETUP)/PromotionExtras"
- $(_v) $(INSTALL_FILE) "$(Sources)/contrib/migration/calendarpromotion.py" "$(DSTROOT)$(SERVERSETUP)/PromotionExtras/59_calendarpromotion.py"
- $(_v) chmod ugo+x "$(DSTROOT)$(SERVERSETUP)/PromotionExtras/59_calendarpromotion.py"
- @echo "Installing server uninstall extras script..."
- $(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SERVERSETUP)/UninstallExtras"
- $(_v) $(INSTALL_FILE) "$(Sources)/contrib/migration/calendardemotion.py" "$(DSTROOT)$(SERVERSETUP)/UninstallExtras/59_calendardemotion.py"
- $(_v) chmod ugo+x "$(DSTROOT)$(SERVERSETUP)/UninstallExtras/59_calendardemotion.py"
@echo "Installing changeip script..."
$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(SIPP)$(LIBEXECDIR)/changeip"
$(_v) $(INSTALL_FILE) "$(Sources)/calendarserver/tools/changeip_calendar.py" "$(DSTROOT)$(SIPP)$(LIBEXECDIR)/changeip/changeip_calendar.py"
Modified: CalendarServer/trunk/twistedcaldav/config.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/config.py 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/twistedcaldav/config.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -33,7 +33,7 @@
class ConfigDict(dict):
"""
Dictionary which can be accessed using attribute syntax, because
- that reads an writes nicer in code. For example:
+ that reads and writes nicer in code. For example:
C{config.Thingo.Tiny.Tweak}
instead of:
C{config["Thingo"]["Tiny"]["Tweak"]}
@@ -211,7 +211,7 @@
self.reset()
def updateDefaults(self, items):
- _mergeData(self._provider.getDefaults(), items)
+ mergeData(self._provider.getDefaults(), items)
self.update(items)
def update(self, items=None, reloading=False):
@@ -225,7 +225,7 @@
# Call hooks
for hook in self._preUpdateHooks:
hook(self._data, items, reloading=reloading)
- _mergeData(self._data, items)
+ mergeData(self._data, items)
for hook in self._postUpdateHooks:
hook(self._data, reloading=reloading)
@@ -234,7 +234,7 @@
def load(self, configFile):
self._provider.setConfigFileName(configFile)
- configDict = ConfigDict(self._provider.loadConfig())
+ configDict = self._provider.loadConfig()
if not self._provider.hasErrors():
self.update(configDict)
else:
@@ -242,7 +242,7 @@
% (self._provider.getConfigFileName(),))
def reload(self):
- configDict = ConfigDict(self._provider.loadConfig())
+ configDict = self._provider.loadConfig()
if not self._provider.hasErrors():
if self._beforeResetHook:
# Give the beforeResetHook a chance to stash away values we want
@@ -263,7 +263,15 @@
self._data = ConfigDict(copy.deepcopy(self._provider.getDefaults()))
self._dirty = True
-def _mergeData(oldData, newData):
+def mergeData(oldData, newData):
+ """
+ Merge two ConfigDict objects; oldData will be updated with all the keys
+ and values from newData
+ @param oldData: the object to modify
+ @type oldData: ConfigDict
+ @param newData: the object to copy data from
+ @type newData: ConfigDict
+ """
for key, value in newData.iteritems():
if isinstance(value, (dict,)):
if key in oldData:
@@ -271,7 +279,7 @@
"%r in %r is not a ConfigDict" % (oldData[key], oldData)
else:
oldData[key] = {}
- _mergeData(oldData[key], value)
+ mergeData(oldData[key], value)
else:
oldData[key] = value
Modified: CalendarServer/trunk/twistedcaldav/scheduling/imip/inbound.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/imip/inbound.py 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/twistedcaldav/scheduling/imip/inbound.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -21,6 +21,7 @@
from calendarserver.tap.util import FakeRequest
import email.utils
from twext.enterprise.dal.record import fromTable
+from twext.enterprise.dal.syntax import Delete
from twext.enterprise.queue import WorkItem
from twext.python.log import Logger, LoggingMixIn
from twisted.application import service
@@ -78,11 +79,15 @@
class IMIPPollingWork(WorkItem, fromTable(schema.IMIP_POLLING_WORK)):
- # FIXME: delete all other polling work items
# FIXME: purge all old tokens here
+ group = "imip_polling"
@inlineCallbacks
def doWork(self):
+
+ # Delete all other work items
+ yield Delete(From=self.table, Where=None).on(self.transaction)
+
mailRetriever = self.transaction._mailRetriever
if mailRetriever is not None:
try:
Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -29,8 +29,8 @@
from twext.python.log import clearLogLevels, setLogLevelForNamespace
from twistedcaldav import caldavxml, customxml, carddavxml, mkcolxml
-from twistedcaldav.config import ConfigProvider, ConfigurationError
-from twistedcaldav.config import config, _mergeData, fullServerPath
+from twistedcaldav.config import ConfigProvider, ConfigurationError, ConfigDict
+from twistedcaldav.config import config, mergeData, fullServerPath
from twistedcaldav.util import getPasswordFromKeychain
from twistedcaldav.util import KeychainAccessError, KeychainPasswordNotFound
@@ -41,7 +41,7 @@
log = Logger()
if platform.isMacOSX():
- DEFAULT_CONFIG_FILE = "/Library/Server/Calendar and Contacts/Config/caldavd.plist"
+ DEFAULT_CONFIG_FILE = "/Applications/Server.app/Contents/ServerRoot/private/etc/caldavd/caldavd-apple.plist"
else:
DEFAULT_CONFIG_FILE = "/etc/caldavd/caldavd.plist"
@@ -975,7 +975,10 @@
# America/Los_Angeles.
"DefaultTimezone" : "",
+ # These two aren't relative to ConfigRoot:
"Includes": [], # Other plists to parse after this one
+ "WritableConfigFile" : "", # which config file calendarserver_config should
+ # write to for changes; empty string means the main config file.
}
@@ -1003,15 +1006,19 @@
configDict = {}
if self._configFileName:
configDict = self._parseConfigFromFile(self._configFileName)
+ configDict = ConfigDict(configDict)
# Now check for Includes and parse and add each of those
if "Includes" in configDict:
- configRoot = os.path.join(configDict.ServerRoot, configDict.ConfigRoot)
for include in configDict.Includes:
- path = _expandPath(fullServerPath(configRoot, include))
- additionalDict = self._parseConfigFromFile(path)
- if additionalDict:
- log.info("Adding configuration from file: '%s'" % (path,))
- configDict.update(additionalDict)
+ # Includes are not relative to ConfigRoot
+ path = _expandPath(include)
+ if os.path.exists(path):
+ additionalDict = ConfigDict(self._parseConfigFromFile(path))
+ if additionalDict:
+ log.info("Adding configuration from file: '%s'" % (path,))
+ mergeData(configDict, additionalDict)
+ else:
+ log.warn("Missing configuration file: '%s'" % (path,))
return configDict
@@ -1028,7 +1035,6 @@
return configDict
-
def _expandPath(path):
if '$' in path:
return path.replace('$', getfqdn())
@@ -1067,7 +1073,6 @@
Post-update configuration hook for making all configured paths relative to
their respective root directories rather than the current working directory.
"""
-
# Remove possible trailing slash from ServerRoot
try:
configDict["ServerRoot"] = configDict["ServerRoot"].rstrip("/")
@@ -1130,7 +1135,7 @@
if dsType == configDict.DirectoryService.type:
oldParams = configDict.DirectoryService.params
newParams = items.DirectoryService.get("params", {})
- _mergeData(oldParams, newParams)
+ mergeData(oldParams, newParams)
else:
if dsType in DEFAULT_SERVICE_PARAMS:
configDict.DirectoryService.params = copy.deepcopy(DEFAULT_SERVICE_PARAMS[dsType])
@@ -1160,7 +1165,7 @@
if dsType == configDict.ResourceService.type:
oldParams = configDict.ResourceService.params
newParams = items.ResourceService.get("params", {})
- _mergeData(oldParams, newParams)
+ mergeData(oldParams, newParams)
else:
if dsType in DEFAULT_RESOURCE_PARAMS:
configDict.ResourceService.params = copy.deepcopy(DEFAULT_RESOURCE_PARAMS[dsType])
@@ -1192,7 +1197,7 @@
if dsType == configDict.DirectoryAddressBook.type:
oldParams = configDict.DirectoryAddressBook.params
newParams = items["DirectoryAddressBook"].get("params", {})
- _mergeData(oldParams, newParams)
+ mergeData(oldParams, newParams)
else:
if dsType in directoryAddressBookBackingServiceDefaultParams:
configDict.DirectoryAddressBook.params = copy.deepcopy(directoryAddressBookBackingServiceDefaultParams[dsType])
@@ -1203,7 +1208,7 @@
if param not in directoryAddressBookBackingServiceDefaultParams[dsType]:
raise ConfigurationError("Parameter %s is not supported by service %s" % (param, dsType))
- _mergeData(configDict, items)
+ mergeData(configDict, items)
for param in tuple(configDict.DirectoryAddressBook.params):
if param not in directoryAddressBookBackingServiceDefaultParams[configDict.DirectoryAddressBook.type]:
Modified: CalendarServer/trunk/twistedcaldav/test/test_config.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_config.py 2013-03-14 19:43:14 UTC (rev 10923)
+++ CalendarServer/trunk/twistedcaldav/test/test_config.py 2013-03-14 21:14:13 UTC (rev 10924)
@@ -17,7 +17,7 @@
from twext.python.plistlib import writePlist #@UnresolvedImport
from twext.python.log import logLevelForNamespace
-from twistedcaldav.config import config, ConfigDict
+from twistedcaldav.config import config, ConfigDict, mergeData
from twistedcaldav.resource import CalDAVResource
from twistedcaldav.stdconfig import DEFAULT_CONFIG, PListConfigProvider,\
RELATIVE_PATHS
@@ -371,6 +371,40 @@
configDict._x = "X"
self.assertEquals(configDict._x, "X")
+ def test_mergeData(self):
+ """
+ Verify we don't lose keys which are present in the old but not
+ replaced in the new.
+ """
+ old = ConfigDict({
+ "Scheduling" : ConfigDict({
+ "iMIP" : ConfigDict({
+ "Enabled" : True,
+ "Receiving" : ConfigDict({
+ "Username" : "xyzzy",
+ "Server" : "example.com",
+ }),
+ "Sending" : ConfigDict({
+ "Username" : "plugh",
+ }),
+ "AddressPatterns" : ["mailto:.*"],
+ }),
+ }),
+ })
+ new = ConfigDict({
+ "Scheduling" : ConfigDict({
+ "iMIP" : ConfigDict({
+ "Enabled" : False,
+ "Receiving" : ConfigDict({
+ "Username" : "changed",
+ }),
+ }),
+ }),
+ })
+ mergeData(old, new)
+ self.assertEquals(old.Scheduling.iMIP.Receiving.Server, "example.com")
+ self.assertEquals(old.Scheduling.iMIP.Sending.Username, "plugh")
+
def test_SimpleInclude(self):
testConfigMaster = """<?xml version="1.0" encoding="UTF-8"?>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130314/e0b4455d/attachment-0001.html>
More information about the calendarserver-changes
mailing list