Merge branch 'master' into erp5-component
[slapos.git] / slapos / recipe / librecipe / generic.py
1 ##############################################################################
2 #
3 # Copyright (c) 2010 Vifib SARL and Contributors. All Rights Reserved.
4 #
5 # WARNING: This program as such is intended to be used by professional
6 # programmers who take the whole responsibility of assessing all potential
7 # consequences resulting from its eventual inadequacies and bugs
8 # End users who are looking for a ready-to-use solution with commercial
9 # guarantees and support are strongly adviced to contract a Free Software
10 # Service Company
11 #
12 # This program is Free Software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation; either version 3
15 # of the License, or (at your option) any later version.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 #
26 ##############################################################################
27 import logging
28 import os
29 import sys
30 import inspect
31 import re
32 import urllib
33 import urlparse
34
35 import pkg_resources
36 import zc.buildout
37
38 class GenericBaseRecipe(object):
39 """Boilerplate class for all Buildout recipes providing helpful methods like
40 creating configuration file, creating wrappers, generating passwords, etc.
41 Can be extended in SlapOS recipes to ease development.
42 """
43
44 TRUE_VALUES = ['y', 'yes', '1', 'true']
45 FALSE_VALUES = ['n', 'no', '0', 'false']
46
47 def __init__(self, buildout, name, options):
48 """Recipe initialisation"""
49 self.name = name
50 self.buildout = buildout
51 self.logger = logging.getLogger(name)
52
53 self.options = options.copy() # If _options use self.optionIsTrue
54 self._options(options) # Options Hook
55 self.options = options.copy() # Updated options dict
56
57 self._ws = self.getWorkingSet()
58
59 def update(self):
60 """By default update method does the same thing than install"""
61 return self.install()
62
63 def install(self):
64 """Install method of the recipe. This must be overriden in child
65 classes """
66 raise NotImplementedError("install method is not implemented.")
67
68 def getWorkingSet(self):
69 """If you want do override the default working set"""
70 egg = zc.recipe.egg.Egg(self.buildout, 'slapos.cookbook',
71 self.options.copy())
72 requirements, ws = egg.working_set()
73 return ws
74
75 def _options(self, options):
76 """Options Hook method. This method can be overriden in child classes"""
77 return
78
79 def createFile(self, name, content, mode=0600):
80 """Create a file with content
81
82 The parent directory should exists, else it would raise IOError"""
83 with open(name, 'w') as fileobject:
84 fileobject.write(content)
85 os.chmod(fileobject.name, mode)
86 return os.path.abspath(name)
87
88 def createExecutable(self, name, content, mode=0700):
89 return self.createFile(name, content, mode)
90
91 def createPythonScript(self, name, absolute_function, arguments=''):
92 """Create a python script using zc.buildout.easy_install.scripts
93
94 * function should look like 'module.function', or only 'function'
95 if it is a builtin function."""
96 absolute_function = tuple(absolute_function.rsplit('.', 1))
97 if len(absolute_function) == 1:
98 absolute_function = ('__builtin__',) + absolute_function
99 if len(absolute_function) != 2:
100 raise ValueError("A non valid function was given")
101
102 module, function = absolute_function
103 path, filename = os.path.split(os.path.abspath(name))
104
105 script = zc.buildout.easy_install.scripts(
106 [(filename, module, function)], self._ws, sys.executable,
107 path, arguments=arguments)[0]
108 return script
109
110 def createDirectory(self, parent, name, mode=0700):
111 path = os.path.join(parent, name)
112 if not os.path.exists(path):
113 os.mkdir(path, mode)
114 elif not os.path.isdir(path):
115 raise OSError("%r exists but is not a directory." % name)
116 return path
117
118 def substituteTemplate(self, template_location, mapping_dict):
119 """Read from file template_location an substitute content with
120 mapping_dict doing a dummy python format."""
121 with open(template_location, 'r') as template:
122 return template.read() % mapping_dict
123
124 def getTemplateFilename(self, template_name):
125 caller = inspect.stack()[1]
126 caller_frame = caller[0]
127 name = caller_frame.f_globals['__name__']
128 return pkg_resources.resource_filename(name,
129 'template/%s' % template_name)
130
131 def generatePassword(self, len_=32):
132 """
133 The purpose of this method is to generate a password which doesn't change
134 from one execution to the next, so the generated password doesn't change
135 on each slapgrid-cp execution.
136
137 Currently, it returns a hardcoded password because no decision has been
138 taken on where a generated password should be kept (so it is generated
139 once only).
140 """
141 # TODO: implement a real password generator which remember the last
142 # call.
143 return "insecure"
144
145 def isTrueValue(self, value):
146 return str(value).lower() in GenericBaseRecipe.TRUE_VALUES
147
148 def optionIsTrue(self, optionname, default=None):
149 if default is not None and optionname not in self.options:
150 return default
151 return self.isTrueValue(self.options[optionname])
152
153 def unparseUrl(self, scheme, host, path='', params='', query='',
154 fragment='', port=None, auth=None):
155 """Join a url with auth, host, and port.
156
157 * auth can be either a login string or a tuple (login, password).
158 * if the host is an ipv6 address, brackets will be added to surround it.
159
160 """
161 netloc = ''
162 if auth is not None:
163 auth = tuple(auth)
164 netloc = urllib.quote(str(auth[0])) # Login
165 if len(auth) > 1:
166 netloc += ':%s' % urllib.quote(auth[1]) # Password
167 netloc += '@'
168
169 # host is an ipv6 address whithout brackets
170 if ':' in host and not re.match(r'^\[.*\]$', host):
171 netloc += '[%s]' % host
172 else:
173 netloc += str(host)
174
175 if port is not None:
176 netloc += ':%s' % port
177
178 url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
179
180 return url