1 | #!/usr/bin/env python |
---|
2 | """Install NDG Server package with M2Crypto build settings and to include |
---|
3 | Twisted |
---|
4 | |
---|
5 | NERC Data Grid Project |
---|
6 | """ |
---|
7 | __author__ = "P J Kershaw" |
---|
8 | __date__ = "15/03/07" |
---|
9 | __copyright__ = "(C) 2007 STFC & NERC" |
---|
10 | __license__ = \ |
---|
11 | """This software may be distributed under the terms of the Q Public |
---|
12 | License, version 1.0 or later.""" |
---|
13 | __contact__ = "P.J.Kershaw@rl.ac.uk" |
---|
14 | __revision__ = '$Id$' |
---|
15 | |
---|
16 | |
---|
17 | import os, sys |
---|
18 | import pkg_resources # Locate ndg.security.server.conf dir |
---|
19 | import shutil # For creating config dir |
---|
20 | import urllib |
---|
21 | import optparse |
---|
22 | from ConfigParser import SafeConfigParser |
---|
23 | from subprocess import call |
---|
24 | from setuptools.command.easy_install import main |
---|
25 | |
---|
26 | import logging |
---|
27 | log = logging.getLogger(__name__) |
---|
28 | |
---|
29 | class SecurityInstallError(Exception): |
---|
30 | """Errors related to security installation""" |
---|
31 | |
---|
32 | class SecurityInstall(object): |
---|
33 | '''Wrapper class for NDG security installation |
---|
34 | |
---|
35 | A wrapper is required over and above easy_install as additional setup |
---|
36 | steps are required to enable for example custom build settings for |
---|
37 | M2Crypto |
---|
38 | |
---|
39 | @cvar dependencyLink: default location for dependencies |
---|
40 | @type dependencyLink: string |
---|
41 | |
---|
42 | @cvar defaultTwistedURI: default location for Twisted download |
---|
43 | @type param: string |
---|
44 | |
---|
45 | @cvar configDir: default location for configuration directory "conf" |
---|
46 | @type configDir: string''' |
---|
47 | |
---|
48 | dependencyLink = "http://ndg.nerc.ac.uk/dist/" |
---|
49 | defaultTwistedURI = \ |
---|
50 | 'http://tmrc.mit.edu/mirror/twisted/Twisted/2.2/TwistedSumo-2006-02-12.tar.bz2' |
---|
51 | configDir = os.path.join("/etc", "ndg", "security", "conf") |
---|
52 | |
---|
53 | def __call__(self): |
---|
54 | self.main() |
---|
55 | |
---|
56 | def main(self): |
---|
57 | '''Parse command line args and execute the installation''' |
---|
58 | |
---|
59 | parser = optparse.OptionParser() |
---|
60 | |
---|
61 | parser.add_option("-a", |
---|
62 | "--install-all", |
---|
63 | dest="installAll", |
---|
64 | action="store_true", |
---|
65 | default=False, |
---|
66 | help="Install client AND server packages.") |
---|
67 | |
---|
68 | parser.add_option("-c", |
---|
69 | "--install-client", |
---|
70 | dest="installClient", |
---|
71 | action="store_true", |
---|
72 | default=False, |
---|
73 | help="Install client package only.") |
---|
74 | |
---|
75 | parser.add_option("-s", |
---|
76 | "--install-server", |
---|
77 | dest="installServer", |
---|
78 | action="store_true", |
---|
79 | default=False, |
---|
80 | help="Install server package only.") |
---|
81 | |
---|
82 | parser.add_option("-u", |
---|
83 | "--install-unittests", |
---|
84 | dest="installUnitTests", |
---|
85 | action="store_true", |
---|
86 | default=False, |
---|
87 | help="Install unit test package only.") |
---|
88 | |
---|
89 | parser.add_option("-o", |
---|
90 | "--openssl-path", |
---|
91 | dest="opensslPath", |
---|
92 | default='/usr/local/ssl', |
---|
93 | help="Path to openssl for M2Crypto to link with") |
---|
94 | |
---|
95 | parser.add_option("-n", |
---|
96 | "--no-twisted", |
---|
97 | dest="noTwisted", |
---|
98 | action="store_true", |
---|
99 | default=False, |
---|
100 | help=\ |
---|
101 | """Skip Twisted install. This option applies to the \"all\" and \"server\" |
---|
102 | package options only. Twisted is not needed for the client.""") |
---|
103 | |
---|
104 | parser.add_option("-t", |
---|
105 | "--twisted-uri", |
---|
106 | dest="twistedURI", |
---|
107 | default=self.__class__.defaultTwistedURI, |
---|
108 | help=\ |
---|
109 | """Provide an alternative location for Twisted download. A .tar.bz type file |
---|
110 | is expected. The default is \"%s\"""" % self.__class__.defaultTwistedURI) |
---|
111 | |
---|
112 | parser.add_option("-f", |
---|
113 | "--find-links", |
---|
114 | dest="dependencyLinks", |
---|
115 | default=self.__class__.dependencyLink, |
---|
116 | help=\ |
---|
117 | 'Set URLs to locate packages. The default is "%s"' % \ |
---|
118 | self.__class__.dependencyLink) |
---|
119 | |
---|
120 | parser.add_option("-U", |
---|
121 | "--upgrade", |
---|
122 | dest="upgrade", |
---|
123 | action="store_true", |
---|
124 | default=False, |
---|
125 | help=\ |
---|
126 | 'force upgrade (search PyPI/dependency links for latest version)') |
---|
127 | |
---|
128 | configOpts = ("-C", "--config-dir") |
---|
129 | parser.add_option(dest="configDir", |
---|
130 | default=self.__class__.configDir, |
---|
131 | help=\ |
---|
132 | """Specify a location for configuration files (server package only). The |
---|
133 | default is \"%s\"""" % self.__class__.configDir, |
---|
134 | *configOpts) |
---|
135 | |
---|
136 | self.opt, args = parser.parse_args() |
---|
137 | |
---|
138 | # Sanity check |
---|
139 | nInstallArgs = sum((self.opt.installClient, |
---|
140 | self.opt.installServer, |
---|
141 | self.opt.installUnitTests, |
---|
142 | self.opt.installAll)) |
---|
143 | if not nInstallArgs: |
---|
144 | parser.error("At least one install option must be set") |
---|
145 | |
---|
146 | elif nInstallArgs > 1: |
---|
147 | parser.error("Only one install option may be set") |
---|
148 | |
---|
149 | # Set M2Crypto build settings in a distutils config file |
---|
150 | self.initM2CryptoDependencies() |
---|
151 | |
---|
152 | # Installation based on flags set |
---|
153 | if self.opt.upgrade: |
---|
154 | args = ['-U'] |
---|
155 | else: |
---|
156 | args = [] |
---|
157 | |
---|
158 | # Add links for dependencies |
---|
159 | args += ['-f', self.opt.dependencyLinks] |
---|
160 | |
---|
161 | if self.opt.installClient: |
---|
162 | log.info("Installing ndg-security-client ...") |
---|
163 | args += ["ndg_security_client"] |
---|
164 | main(args) |
---|
165 | |
---|
166 | elif self.opt.installServer: |
---|
167 | log.info("Installing ndg-security-server ...") |
---|
168 | args += ["ndg_security_server"] |
---|
169 | main(args) |
---|
170 | self.installTwisted() |
---|
171 | |
---|
172 | # Config dir is part of server package only |
---|
173 | self.createConfigDir() |
---|
174 | |
---|
175 | elif self.opt.installUnitTests: |
---|
176 | log.info("Installing ndg-security-test ...") |
---|
177 | args += ["ndg_security_test"] |
---|
178 | main(args) |
---|
179 | self.installTwisted() |
---|
180 | |
---|
181 | elif self.opt.installAll: |
---|
182 | log.info("Installing all ...") |
---|
183 | ndgSecurityArgs = args + ["ndg_security"] |
---|
184 | main(ndgSecurityArgs) |
---|
185 | ndgSecurityTestArgs = args + ["ndg_security_test"] |
---|
186 | main(ndgSecurityArgs) |
---|
187 | self.installTwisted() |
---|
188 | |
---|
189 | # Config dir is part of server package |
---|
190 | self.createConfigDir() |
---|
191 | |
---|
192 | |
---|
193 | def initM2CryptoDependencies(self): |
---|
194 | '''Set-up link path for openssl for M2Crypto build by creating a |
---|
195 | distutils config file containing the include file and library file |
---|
196 | paths''' |
---|
197 | log.info("Initialising M2Crypto set-up ...") |
---|
198 | |
---|
199 | opensslInclPath = os.path.join(self.opt.opensslPath, 'include') |
---|
200 | opensslLibPath = os.path.join(self.opt.opensslPath, 'lib') |
---|
201 | |
---|
202 | distutilsCfgFilePath = os.path.join(sys.prefix, |
---|
203 | 'lib', |
---|
204 | 'python%s' % sys.version[:3], |
---|
205 | 'distutils', |
---|
206 | 'distutils.cfg') |
---|
207 | configParser = SafeConfigParser() |
---|
208 | |
---|
209 | if configParser.read(distutilsCfgFilePath): |
---|
210 | # File already exists |
---|
211 | if not configParser.has_section('build_ext'): |
---|
212 | configParser.add_section('build_ext') |
---|
213 | |
---|
214 | if configParser.has_option('build_ext', 'include_dirs'): |
---|
215 | existingInclDirs=configParser.get('build_ext', 'include_dirs') |
---|
216 | |
---|
217 | if opensslInclPath not in existingInclDirs.split(): |
---|
218 | includeDirs = "%s %s" % (opensslInclPath,existingInclDirs) |
---|
219 | configParser.set('build_ext', 'include_dirs', includeDirs) |
---|
220 | else: |
---|
221 | configParser.set('build_ext', 'include_dirs', opensslInclPath) |
---|
222 | |
---|
223 | if configParser.has_option('build_ext', 'library_dirs'): |
---|
224 | existingLibDirs = configParser.get('build_ext','library_dirs') |
---|
225 | |
---|
226 | if opensslLibPath not in existingLibDirs.split(): |
---|
227 | libraryDirs = "%s %s" % (opensslLibPath, existingLibDirs) |
---|
228 | configParser.set('build_ext', 'library_dirs', libraryDirs) |
---|
229 | else: |
---|
230 | configParser.set('build_ext', 'library_dirs', opensslLibPath) |
---|
231 | |
---|
232 | else: |
---|
233 | # No config file present - make one |
---|
234 | configParser.add_section('build_ext') |
---|
235 | configParser.set('build_ext', 'include_dirs', opensslInclPath) |
---|
236 | configParser.set('build_ext', 'library_dirs', opensslLibPath) |
---|
237 | |
---|
238 | configParser.write(open(distutilsCfgFilePath, 'w')) |
---|
239 | |
---|
240 | |
---|
241 | def installTwisted(self): |
---|
242 | '''Download and install twisted manually as it is not egg compatible |
---|
243 | ''' |
---|
244 | |
---|
245 | if self.opt.noTwisted: |
---|
246 | return |
---|
247 | |
---|
248 | log.info("Installing Twisted: %s ..." % self.opt.twistedURI) |
---|
249 | |
---|
250 | # Install Twisted sumo |
---|
251 | try: |
---|
252 | twistedTarBz = os.path.basename(self.opt.twistedURI) |
---|
253 | urllib.urlretrieve(self.opt.twistedURI, twistedTarBz) |
---|
254 | |
---|
255 | except IOError, (errMsg, e): |
---|
256 | raise SecurityInstallError, \ |
---|
257 | 'Error retrieving Twisted from "%s": %s' % \ |
---|
258 | (self.opt.twistedTarURI, e[1]) |
---|
259 | except Exception, e: |
---|
260 | raise SecurityInstallError, \ |
---|
261 | 'Error retrieving Twisted from "%s": %s' % \ |
---|
262 | (self.opt.twistedTarURI, e) |
---|
263 | |
---|
264 | import tarfile |
---|
265 | |
---|
266 | twistedTar = tarfile.open(twistedTarBz, 'r:bz2') |
---|
267 | for tarInfo in twistedTar: |
---|
268 | twistedTar.extract(tarInfo) |
---|
269 | |
---|
270 | try: |
---|
271 | twistedDir=os.path.splitext(os.path.splitext(twistedTarBz)[0])[0] |
---|
272 | except Exception: |
---|
273 | raise SecurityInstallError, \ |
---|
274 | 'Error getting Twisted dir path from tar.bz file name: "%s"' % \ |
---|
275 | twistedTarBz |
---|
276 | |
---|
277 | os.chdir(twistedDir) |
---|
278 | try: |
---|
279 | retCode = call([os.path.join(sys.prefix, 'bin', 'python'), |
---|
280 | 'setup.py', |
---|
281 | 'install']) |
---|
282 | except OSError, e: |
---|
283 | raise SecurityInstallError, \ |
---|
284 | "Error calling setup install for Twisted: " + str(e) |
---|
285 | |
---|
286 | if retCode != 0: |
---|
287 | raise SecurityInstallError, "Twisted setup install returned %d" %\ |
---|
288 | retCode |
---|
289 | |
---|
290 | os.chdir('..') |
---|
291 | |
---|
292 | |
---|
293 | def createConfigDir(self): |
---|
294 | """Copy configuration files for services from the server egg into |
---|
295 | a config area. The default is /etc/ndg/security/conf""" |
---|
296 | |
---|
297 | # Skip if not set |
---|
298 | if not self.opt.configDir: |
---|
299 | return |
---|
300 | |
---|
301 | log.info('Copying configuration directory to "%s"'%self.opt.configDir) |
---|
302 | |
---|
303 | # Otherwise create - fix to rwx for owner and group only |
---|
304 | confDirPath = os.path.dirname(self.opt.configDir) |
---|
305 | try: |
---|
306 | os.makedirs(confDirPath, mode=0770) |
---|
307 | except OSError, e: |
---|
308 | if e.errno != 17: |
---|
309 | # errno=17 -> file already exists - it's OK if directory is |
---|
310 | # already present |
---|
311 | raise SecurityInstallError, \ |
---|
312 | "Creating configuration directory: %s" % e |
---|
313 | |
---|
314 | # Locate conf directory in active egg |
---|
315 | eggConfigDir = pkg_resources.resource_filename('ndg.security.server', |
---|
316 | 'conf') |
---|
317 | # Get distribution version info |
---|
318 | serverDistro = pkg_resources.get_distribution('ndg-security-server') |
---|
319 | |
---|
320 | configDirVers = "%s.%s" % (self.opt.configDir, serverDistro.version) |
---|
321 | # Copy over conf directory from egg |
---|
322 | try: |
---|
323 | shutil.copytree(eggConfigDir, configDirVers) |
---|
324 | except OSError, e: |
---|
325 | if e.errno != 17: |
---|
326 | raise SecurityInstallError, \ |
---|
327 | "Copying configuration directory: %s" % e |
---|
328 | |
---|
329 | try: |
---|
330 | os.symlink(configDirVers, self.opt.configDir) |
---|
331 | except OSError, e: |
---|
332 | raise SecurityInstallError, \ |
---|
333 | "Making a symbolic link %s -> %s: %s" % (self.opt.configDir, |
---|
334 | configDirVers, |
---|
335 | e) |
---|
336 | if __name__ == "__main__": |
---|
337 | SecurityInstall()() |
---|
338 | |
---|