vendor/CMF/1.5.3/CMFSetup

view tool.py @ 0:3ed006215eb6

Vendor import of CMF 1.5.3
author fguillaume
date Tue, 09 Aug 2005 10:47:34 +0000
parents
children
line source
1 ##############################################################################
2 #
3 # Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
4 #
5 # This software is subject to the provisions of the Zope Public License,
6 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10 # FOR A PARTICULAR PURPOSE.
11 #
12 ##############################################################################
13 """ Classes: SetupTool
15 $Id: tool.py 36916 2005-04-11 11:33:17Z yuppie $
16 """
18 import os
19 import time
20 from cgi import escape
22 from AccessControl import ClassSecurityInfo
23 from Acquisition import aq_base
24 from Globals import InitializeClass
25 from OFS.Folder import Folder
26 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
28 from Products.CMFCore.utils import UniqueObject
29 from Products.CMFCore.utils import getToolByName
31 from interfaces import EXTENSION
32 from interfaces import ISetupTool
33 from permissions import ManagePortal
34 from context import DirectoryImportContext
35 from context import SnapshotImportContext
36 from context import TarballExportContext
37 from context import SnapshotExportContext
38 from differ import ConfigDiff
39 from registry import ImportStepRegistry
40 from registry import ExportStepRegistry
41 from registry import ToolsetRegistry
42 from registry import _profile_registry
44 from utils import _resolveDottedName
45 from utils import _wwwdir
47 IMPORT_STEPS_XML = 'import_steps.xml'
48 EXPORT_STEPS_XML = 'export_steps.xml'
49 TOOLSET_XML = 'toolset.xml'
51 def exportStepRegistries( context ):
53 """ Built-in handler for exporting import / export step registries.
54 """
55 site = context.getSite()
56 tool = getToolByName( site, 'portal_setup' )
58 import_steps_xml = tool.getImportStepRegistry().generateXML()
59 context.writeDataFile( 'import_steps.xml', import_steps_xml, 'text/xml' )
61 export_steps_xml = tool.getExportStepRegistry().generateXML()
62 context.writeDataFile( 'export_steps.xml', export_steps_xml, 'text/xml' )
64 return 'Step registries exported'
66 def importToolset( context ):
68 """ Import required / forbidden tools from XML file.
69 """
70 site = context.getSite()
71 encoding = context.getEncoding()
73 xml = context.readDataFile(TOOLSET_XML)
74 if xml is None:
75 return 'Toolset: Nothing to import.'
77 setup_tool = getToolByName( site, 'portal_setup' )
78 toolset = setup_tool.getToolsetRegistry()
80 toolset.parseXML(xml, encoding)
82 existing_ids = site.objectIds()
83 existing_values = site.objectValues()
85 for tool_id in toolset.listForbiddenTools():
87 if tool_id in existing_ids:
88 site._delObject( tool_id )
90 for info in toolset.listRequiredToolInfo():
92 tool_id = str( info[ 'id' ] )
93 tool_class = _resolveDottedName( info[ 'class' ] )
95 existing = getToolByName( site, tool_id, None )
97 if existing is None:
98 site._setObject( tool_id, tool_class() )
100 else:
101 unwrapped = aq_base( existing )
102 if not isinstance( unwrapped, tool_class ):
103 site._delObject( tool_id )
104 site._setObject( tool_id, tool_class() )
106 return 'Toolset imported.'
108 def exportToolset( context ):
110 """ Export required / forbidden tools to XML file.
111 """
112 site = context.getSite()
113 setup_tool = getToolByName( site, 'portal_setup' )
114 toolset = setup_tool.getToolsetRegistry()
116 xml = toolset.generateXML()
117 context.writeDataFile( TOOLSET_XML, xml, 'text/xml' )
119 return 'Toolset exported.'
122 class SetupTool( UniqueObject, Folder ):
124 """ Profile-based site configuration manager.
125 """
126 __implements__ = ( ISetupTool, ) + Folder.__implements__
128 id = 'portal_setup'
129 meta_type = 'Portal Setup Tool'
131 _import_context_id = ''
133 security = ClassSecurityInfo()
135 def __init__( self ):
137 self._import_registry = ImportStepRegistry()
138 self._export_registry = ExportStepRegistry()
139 self._export_registry.registerStep( 'step_registries'
140 , exportStepRegistries
141 , 'Export import / export steps.'
142 )
143 self._toolset_registry = ToolsetRegistry()
145 #
146 # ISetupTool API
147 #
148 security.declareProtected( ManagePortal, 'getEncoding' )
149 def getEncoding( self ):
151 """ See ISetupTool.
152 """
153 return 'ascii'
155 security.declareProtected( ManagePortal, 'getImportContextId' )
156 def getImportContextID( self ):
158 """ See ISetupTool.
159 """
160 return self._import_context_id
162 security.declareProtected(ManagePortal, 'setImportContext')
163 def setImportContext(self, context_id, encoding=None):
165 """ See ISetupTool.
166 """
167 self._import_context_id = context_id
169 self._updateImportStepsRegistry( encoding )
170 self._updateExportStepsRegistry( encoding )
171 self._updateToolsetRegistry( encoding )
173 security.declareProtected( ManagePortal, 'getImportStepRegistry' )
174 def getImportStepRegistry( self ):
176 """ See ISetupTool.
177 """
178 return self._import_registry
180 security.declareProtected( ManagePortal, 'getImportStepRegistry' )
181 def getExportStepRegistry( self ):
183 """ See ISetupTool.
184 """
185 return self._export_registry
187 security.declareProtected( ManagePortal, 'getToolsetRegistry' )
188 def getToolsetRegistry( self ):
190 """ See ISetupTool.
191 """
192 return self._toolset_registry
194 security.declareProtected( ManagePortal, 'executeStep' )
195 def runImportStep( self, step_id, run_dependencies=True, purge_old=None ):
197 """ See ISetupTool.
198 """
199 context = self._getImportContext(self._import_context_id, purge_old)
201 info = self._import_registry.getStepMetadata( step_id )
203 if info is None:
204 raise ValueError, 'No such import step: %s' % step_id
206 dependencies = info.get( 'dependencies', () )
208 messages = {}
209 steps = []
210 if run_dependencies:
211 for dependency in dependencies:
213 if dependency not in steps:
214 message = self._doRunImportStep( dependency, context )
215 messages[ dependency ] = message
216 steps.append( dependency )
218 message = self._doRunImportStep( step_id, context )
219 messages[ step_id ] = message
220 steps.append( step_id )
222 return { 'steps' : steps, 'messages' : messages }
224 security.declareProtected( ManagePortal, 'runAllSetupSteps')
225 def runAllImportSteps( self, purge_old=None ):
227 """ See ISetupTool.
228 """
229 context = self._getImportContext(self._import_context_id, purge_old)
231 steps = self._import_registry.sortSteps()
232 messages = {}
234 for step in steps:
235 message = self._doRunImportStep( step, context )
236 messages[ step ] = message
238 return { 'steps' : steps, 'messages' : messages }
240 security.declareProtected( ManagePortal, 'runExportStep')
241 def runExportStep( self, step_id ):
243 """ See ISetupTool.
244 """
245 return self._doRunExportSteps( [ step_id ] )
247 security.declareProtected(ManagePortal, 'runAllExportSteps')
248 def runAllExportSteps( self ):
250 """ See ISetupTool.
251 """
252 return self._doRunExportSteps( self._export_registry.listSteps() )
254 security.declareProtected( ManagePortal, 'createSnapshot')
255 def createSnapshot( self, snapshot_id ):
257 """ See ISetupTool.
258 """
259 context = SnapshotExportContext( self, snapshot_id )
260 messages = {}
261 steps = self._export_registry.listSteps()
263 for step_id in steps:
265 handler = self._export_registry.getStep( step_id )
267 if handler is None:
268 raise ValueError( 'Invalid export step: %s' % step_id )
270 messages[ step_id ] = handler( context )
273 return { 'steps' : steps
274 , 'messages' : messages
275 , 'url' : context.getSnapshotURL()
276 , 'snapshot' : context.getSnapshotFolder()
277 }
279 security.declareProtected(ManagePortal, 'compareConfigurations')
280 def compareConfigurations( self
281 , lhs_context
282 , rhs_context
283 , missing_as_empty=False
284 , ignore_blanks=False
285 , skip=( 'CVS', '.svn' )
286 ):
287 """ See ISetupTool.
288 """
289 differ = ConfigDiff( lhs_context
290 , rhs_context
291 , missing_as_empty
292 , ignore_blanks
293 , skip
294 )
296 return differ.compare()
298 security.declareProtected( ManagePortal, 'markupComparison')
299 def markupComparison(self, lines):
301 """ See ISetupTool.
302 """
303 result = []
305 for line in lines.splitlines():
307 if line.startswith('** '):
309 if line.find('File') > -1:
310 if line.find('replaced') > -1:
311 result.append( ( 'file-to-dir', line ) )
312 elif line.find('added') > -1:
313 result.append( ( 'file-added', line ) )
314 else:
315 result.append( ( 'file-removed', line ) )
316 else:
317 if line.find('replaced') > -1:
318 result.append( ( 'dir-to-file', line ) )
319 elif line.find('added') > -1:
320 result.append( ( 'dir-added', line ) )
321 else:
322 result.append( ( 'dir-removed', line ) )
324 elif line.startswith('@@'):
325 result.append( ( 'diff-range', line ) )
327 elif line.startswith(' '):
328 result.append( ( 'diff-context', line ) )
330 elif line.startswith('+'):
331 result.append( ( 'diff-added', line ) )
333 elif line.startswith('-'):
334 result.append( ( 'diff-removed', line ) )
336 elif line == '\ No newline at end of file':
337 result.append( ( 'diff-context', line ) )
339 else:
340 result.append( ( 'diff-header', line ) )
342 return '<pre>\n%s\n</pre>' % (
343 '\n'.join( [ ( '<span class="%s">%s</span>' % ( cl, escape( l ) ) )
344 for cl, l in result] ) )
346 #
347 # ZMI
348 #
349 manage_options = ( Folder.manage_options[ :1 ]
350 + ( { 'label' : 'Properties'
351 , 'action' : 'manage_tool'
352 }
353 , { 'label' : 'Import'
354 , 'action' : 'manage_importSteps'
355 }
356 , { 'label' : 'Export'
357 , 'action' : 'manage_exportSteps'
358 }
359 , { 'label' : 'Snapshots'
360 , 'action' : 'manage_snapshots'
361 }
362 , { 'label' : 'Comparison'
363 , 'action' : 'manage_showDiff'
364 }
365 )
366 + Folder.manage_options[ 3: ] # skip "View", "Properties"
367 )
369 security.declareProtected( ManagePortal, 'manage_tool' )
370 manage_tool = PageTemplateFile( 'sutProperties', _wwwdir )
372 security.declareProtected( ManagePortal, 'manage_updateToolProperties' )
373 def manage_updateToolProperties(self, context_id, RESPONSE):
374 """ Update the tool's settings.
375 """
376 self.setImportContext(context_id)
378 RESPONSE.redirect( '%s/manage_tool?manage_tabs_message=%s'
379 % ( self.absolute_url(), 'Properties+updated.' )
380 )
382 security.declareProtected( ManagePortal, 'manage_importSteps' )
383 manage_importSteps = PageTemplateFile( 'sutImportSteps', _wwwdir )
385 security.declareProtected( ManagePortal, 'manage_importSelectedSteps' )
386 def manage_importSelectedSteps( self
387 , ids
388 , run_dependencies
389 , RESPONSE
390 ):
391 """ Import the steps selected by the user.
392 """
393 if not ids:
394 message = 'No+steps+selected.'
396 else:
397 steps_run = []
398 for step_id in ids:
399 result = self.runImportStep( step_id, run_dependencies )
400 steps_run.extend( result[ 'steps' ] )
402 message = 'Steps+run:%s' % '+,'.join( steps_run )
404 RESPONSE.redirect( '%s/manage_importSteps?manage_tabs_message=%s'
405 % ( self.absolute_url(), message )
406 )
408 security.declareProtected( ManagePortal, 'manage_importSelectedSteps' )
409 def manage_importAllSteps( self, RESPONSE ):
411 """ Import all steps.
412 """
413 result = self.runAllImportSteps()
414 message = 'Steps+run:%s' % '+,'.join( result[ 'steps' ] )
416 RESPONSE.redirect( '%s/manage_importSteps?manage_tabs_message=%s'
417 % ( self.absolute_url(), message )
418 )
420 security.declareProtected( ManagePortal, 'manage_exportSteps' )
421 manage_exportSteps = PageTemplateFile( 'sutExportSteps', _wwwdir )
423 security.declareProtected( ManagePortal, 'manage_exportSelectedSteps' )
424 def manage_exportSelectedSteps( self, ids, RESPONSE ):
426 """ Export the steps selected by the user.
427 """
428 if not ids:
429 RESPONSE.redirect( '%s/manage_exportSteps?manage_tabs_message=%s'
430 % ( self.absolute_url(), 'No+steps+selected.' )
431 )
433 result = self._doRunExportSteps( ids )
434 RESPONSE.setHeader( 'Content-type', 'application/x-gzip')
435 RESPONSE.setHeader( 'Content-disposition'
436 , 'attachment; filename=%s' % result[ 'filename' ]
437 )
438 return result[ 'tarball' ]
440 security.declareProtected( ManagePortal, 'manage_exportAllSteps' )
441 def manage_exportAllSteps( self, RESPONSE ):
443 """ Export all steps.
444 """
445 result = self.runAllExportSteps()
446 RESPONSE.setHeader( 'Content-type', 'application/x-gzip')
447 RESPONSE.setHeader( 'Content-disposition'
448 , 'attachment; filename=%s' % result[ 'filename' ]
449 )
450 return result[ 'tarball' ]
452 security.declareProtected( ManagePortal, 'manage_snapshots' )
453 manage_snapshots = PageTemplateFile( 'sutSnapshots', _wwwdir )
455 security.declareProtected( ManagePortal, 'listSnapshotInfo' )
456 def listSnapshotInfo( self ):
458 """ Return a list of mappings describing available snapshots.
460 o Keys include:
462 'id' -- snapshot ID
464 'title' -- snapshot title or ID
466 'url' -- URL of the snapshot folder
467 """
468 result = []
469 snapshots = self._getOb( 'snapshots', None )
471 if snapshots:
473 for id, folder in snapshots.objectItems( 'Folder' ):
475 result.append( { 'id' : id
476 , 'title' : folder.title_or_id()
477 , 'url' : folder.absolute_url()
478 } )
479 return result
481 security.declareProtected( ManagePortal, 'listProfileInfo' )
482 def listProfileInfo( self ):
484 """ Return a list of mappings describing registered profiles.
486 o Keys include:
488 'id' -- profile ID
490 'title' -- profile title or ID
492 'description' -- description of the profile
494 'path' -- path to the profile within its product
496 'product' -- name of the registering product
497 """
498 return _profile_registry.listProfileInfo()
500 security.declareProtected(ManagePortal, 'listContextInfos')
501 def listContextInfos(self):
503 """ List registered profiles and snapshots.
504 """
506 s_infos = [ { 'id': 'snapshot-%s' % info['id'],
507 'title': info['title'] }
508 for info in self.listSnapshotInfo() ]
509 p_infos = [ { 'id': 'profile-%s' % info['id'],
510 'title': info['title'] }
511 for info in self.listProfileInfo() ]
513 return tuple(s_infos + p_infos)
515 security.declareProtected( ManagePortal, 'manage_createSnapshot' )
516 def manage_createSnapshot( self, RESPONSE, snapshot_id=None ):
518 """ Create a snapshot with the given ID.
520 o If no ID is passed, generate one.
521 """
522 if snapshot_id is None:
523 timestamp = time.gmtime()
524 snapshot_id = 'snapshot-%4d%02d%02d%02d%02d%02d' % timestamp[:6]
526 self.createSnapshot( snapshot_id )
528 RESPONSE.redirect( '%s/manage_snapshots?manage_tabs_message=%s'
529 % ( self.absolute_url(), 'Snapshot+created.' ) )
531 security.declareProtected( ManagePortal, 'manage_showDiff' )
532 manage_showDiff = PageTemplateFile( 'sutCompare', _wwwdir )
534 def manage_downloadDiff( self
535 , lhs
536 , rhs
537 , missing_as_empty
538 , ignore_blanks
539 , RESPONSE
540 ):
541 """ Crack request vars and call compareConfigurations.
543 o Return the result as a 'text/plain' stream, suitable for framing.
544 """
545 comparison = self.manage_compareConfigurations( lhs
546 , rhs
547 , missing_as_empty
548 , ignore_blanks
549 )
550 RESPONSE.setHeader( 'Content-Type', 'text/plain' )
551 return _PLAINTEXT_DIFF_HEADER % ( lhs, rhs, comparison )
553 security.declareProtected( ManagePortal, 'manage_compareConfigurations' )
554 def manage_compareConfigurations( self
555 , lhs
556 , rhs
557 , missing_as_empty
558 , ignore_blanks
559 ):
560 """ Crack request vars and call compareConfigurations.
561 """
562 lhs_context = self._getImportContext( lhs )
563 rhs_context = self._getImportContext( rhs )
565 return self.compareConfigurations( lhs_context
566 , rhs_context
567 , missing_as_empty
568 , ignore_blanks
569 )
572 #
573 # Helper methods
574 #
575 security.declarePrivate( '_getProductPath' )
576 def _getProductPath( self, product_name ):
578 """ Return the absolute path of the product's directory.
579 """
580 try:
581 product = __import__( 'Products.%s' % product_name
582 , globals(), {}, ['initialize' ] )
583 except ImportError:
584 raise ValueError, 'Not a valid product name: %s' % product_name
586 return product.__path__[0]
588 security.declarePrivate( '_getImportContext' )
589 def _getImportContext( self, context_id, should_purge=None ):
591 """ Crack ID and generate appropriate import context.
592 """
593 encoding = self.getEncoding()
595 if context_id.startswith( 'profile-' ):
597 context_id = context_id[ len( 'profile-' ): ]
598 info = _profile_registry.getProfileInfo( context_id )
600 if info.get( 'product' ):
601 path = os.path.join( self._getProductPath( info[ 'product' ] )
602 , info[ 'path' ] )
603 else:
604 path = info[ 'path' ]
605 if should_purge is None:
606 should_purge = (info.get('type') != EXTENSION)
607 return DirectoryImportContext(self, path, should_purge, encoding)
609 # else snapshot
610 context_id = context_id[ len( 'snapshot-' ): ]
611 if should_purge is None:
612 should_purge = True
613 return SnapshotImportContext(self, context_id, should_purge, encoding)
615 security.declarePrivate( '_updateImportStepsRegistry' )
616 def _updateImportStepsRegistry( self, encoding ):
618 """ Update our import steps registry from our profile.
619 """
620 context = self._getImportContext(self._import_context_id)
621 xml = context.readDataFile(IMPORT_STEPS_XML)
622 if xml is None:
623 return
625 info_list = self._import_registry.parseXML( xml, encoding )
627 for step_info in info_list:
629 id = step_info[ 'id' ]
630 version = step_info[ 'version' ]
631 handler = _resolveDottedName( step_info[ 'handler' ] )
633 dependencies = tuple( step_info.get( 'dependencies', () ) )
634 title = step_info.get( 'title', id )
635 description = ''.join( step_info.get( 'description', [] ) )
637 self._import_registry.registerStep( id=id
638 , version=version
639 , handler=handler
640 , dependencies=dependencies
641 , title=title
642 , description=description
643 )
645 security.declarePrivate( '_updateExportStepsRegistry' )
646 def _updateExportStepsRegistry( self, encoding ):
648 """ Update our export steps registry from our profile.
649 """
650 context = self._getImportContext(self._import_context_id)
651 xml = context.readDataFile(EXPORT_STEPS_XML)
652 if xml is None:
653 return
655 info_list = self._export_registry.parseXML( xml, encoding )
657 for step_info in info_list:
659 id = step_info[ 'id' ]
660 handler = _resolveDottedName( step_info[ 'handler' ] )
662 title = step_info.get( 'title', id )
663 description = ''.join( step_info.get( 'description', [] ) )
665 self._export_registry.registerStep( id=id
666 , handler=handler
667 , title=title
668 , description=description
669 )
671 security.declarePrivate( '_updateToolsetRegistry' )
672 def _updateToolsetRegistry( self, encoding ):
674 """ Update our toolset registry from our profile.
675 """
676 context = self._getImportContext(self._import_context_id)
677 xml = context.readDataFile(TOOLSET_XML)
678 if xml is None:
679 return
681 self._toolset_registry.parseXML( xml, encoding )
683 security.declarePrivate( '_doRunImportStep' )
684 def _doRunImportStep( self, step_id, context ):
686 """ Run a single import step, using a pre-built context.
687 """
688 handler = self._import_registry.getStep( step_id )
690 if handler is None:
691 raise ValueError( 'Invalid import step: %s' % step_id )
693 return handler( context )
695 security.declarePrivate( '_doRunExportSteps')
696 def _doRunExportSteps( self, steps ):
698 """ See ISetupTool.
699 """
700 context = TarballExportContext( self )
701 messages = {}
703 for step_id in steps:
705 handler = self._export_registry.getStep( step_id )
707 if handler is None:
708 raise ValueError( 'Invalid export step: %s' % step_id )
710 messages[ step_id ] = handler( context )
713 return { 'steps' : steps
714 , 'messages' : messages
715 , 'tarball' : context.getArchive()
716 , 'filename' : context.getArchiveFilename()
717 }
719 InitializeClass( SetupTool )
721 _PLAINTEXT_DIFF_HEADER ="""\
722 Comparing configurations: '%s' and '%s'
724 %s"""