1 # -*- coding: utf-8 -*-
3 ##############################################################################
5 # Copyright (c) 2010 Vifib SARL and Contributors. All Rights Reserved.
7 # WARNING: This program as such is intended to be used by professional
8 # programmers who take the whole responsibility of assessing all potential
9 # consequences resulting from its eventual inadequacies and bugs
10 # End users who are looking for a ready-to-use solution with commercial
11 # guarantees and support are strongly adviced to contract a Free Software
14 # This program is Free Software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; either version 3
17 # of the License, or (at your option) any later version.
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 ##############################################################################
42 from slapos
.recipe
.librecipe
import shlex
44 class GenericBaseRecipe(object):
45 """Boilerplate class for all Buildout recipes providing helpful methods like
46 creating configuration file, creating wrappers, generating passwords, etc.
47 Can be extended in SlapOS recipes to ease development.
50 TRUE_VALUES
= ['y', 'yes', '1', 'true']
51 FALSE_VALUES
= ['n', 'no', '0', 'false']
53 def __init__(self
, buildout
, name
, options
):
54 """Recipe initialisation"""
56 self
.buildout
= buildout
57 self
.logger
= logging
.getLogger(name
)
59 self
.options
= options
.copy() # If _options use self.optionIsTrue
60 self
._options(options
) # Options Hook
61 self
.options
= options
.copy() # Updated options dict
63 self
._ws
= self
.getWorkingSet()
66 """By default update method does the same thing than install"""
70 """Install method of the recipe. This must be overriden in child
72 raise NotImplementedError("install method is not implemented.")
74 def getWorkingSet(self
):
75 """If you want do override the default working set"""
76 egg
= zc
.recipe
.egg
.Egg(self
.buildout
, 'slapos.cookbook',
78 requirements
, ws
= egg
.working_set()
81 def _options(self
, options
):
82 """Options Hook method. This method can be overriden in child classes"""
85 def createFile(self
, name
, content
, mode
=0600):
86 """Create a file with content
88 The parent directory should exists, else it would raise IOError"""
89 with
open(name
, 'w') as fileobject
:
90 fileobject
.write(content
)
91 os
.chmod(fileobject
.name
, mode
)
92 return os
.path
.abspath(name
)
94 def createExecutable(self
, name
, content
, mode
=0700):
95 return self
.createFile(name
, content
, mode
)
97 def addLineToFile(self
, filepath
, line
, encoding
='utf8'):
98 """Append a single line to a text file, if the line does not exist yet.
100 line must be unicode."""
102 if os
.path
.exists(filepath
):
103 lines
= [l
.rstrip('\n') for l
in io
.open(filepath
, 'r', encoding
=encoding
)]
107 if not line
in lines
:
109 with io
.open(filepath
, 'w+', encoding
=encoding
) as f
:
110 f
.write(u
'\n'.join(lines
))
112 def createPythonScript(self
, name
, absolute_function
, arguments
=''):
113 """Create a python script using zc.buildout.easy_install.scripts
115 * function should look like 'module.function', or only 'function'
116 if it is a builtin function."""
117 absolute_function
= tuple(absolute_function
.rsplit('.', 1))
118 if len(absolute_function
) == 1:
119 absolute_function
= ('__builtin__',) + absolute_function
120 if len(absolute_function
) != 2:
121 raise ValueError("A non valid function was given")
123 module
, function
= absolute_function
124 path
, filename
= os
.path
.split(os
.path
.abspath(name
))
126 script
= zc
.buildout
.easy_install
.scripts(
127 [(filename
, module
, function
)], self
._ws
, sys
.executable
,
128 path
, arguments
=arguments
)[0]
131 def createWrapper(self
, name
, command
, parameters
, comments
=[],
132 parameters_extra
=False, environment
=None):
134 Creates a very simple (one command) shell script for process replacement.
135 Takes care of quoting.
138 lines
= [ '#!/bin/sh' ]
140 for comment
in comments
:
141 lines
.append('# %s' % comment
)
144 for key
in environment
:
145 lines
.append('export %s=%s' %
(key
, environment
[key
]))
147 lines
.append('exec %s' % shlex
.quote(command
))
149 for param
in parameters
:
150 if len(lines
[-1]) < 40:
151 lines
[-1] += ' ' + shlex
.quote(param
)
154 lines
.append('\t' + shlex
.quote(param
))
157 # pass-through further parameters
161 content
= '\n'.join(lines
) + '\n'
162 return self
.createFile(name
, content
, 0700)
164 def createDirectory(self
, parent
, name
, mode
=0700):
165 path
= os
.path
.join(parent
, name
)
166 if not os
.path
.exists(path
):
168 elif not os
.path
.isdir(path
):
169 raise OSError("%r exists but is not a directory." % name
)
172 def substituteTemplate(self
, template_location
, mapping_dict
):
173 """Read from file template_location an substitute content with
174 mapping_dict doing a dummy python format."""
175 with
open(template_location
, 'r') as template
:
176 return template
.read() % mapping_dict
178 def getTemplateFilename(self
, template_name
):
179 caller
= inspect
.stack()[1]
180 caller_frame
= caller
[0]
181 name
= caller_frame
.f_globals
['__name__']
182 return pkg_resources
.resource_filename(name
,
183 'template/%s' % template_name
)
185 def generatePassword(self
, len_
=32):
187 The purpose of this method is to generate a password which doesn't change
188 from one execution to the next, so the generated password doesn't change
189 on each slapgrid-cp execution.
191 Currently, it returns a hardcoded password because no decision has been
192 taken on where a generated password should be kept (so it is generated
195 # TODO: implement a real password generator which remember the last
199 def isTrueValue(self
, value
):
200 return str(value
).lower() in GenericBaseRecipe
.TRUE_VALUES
202 def optionIsTrue(self
, optionname
, default
=None):
203 if default
is not None and optionname
not in self
.options
:
205 return self
.isTrueValue(self
.options
[optionname
])
207 def unparseUrl(self
, scheme
, host
, path
='', params
='', query
='',
208 fragment
='', port
=None, auth
=None):
209 """Join a url with auth, host, and port.
211 * auth can be either a login string or a tuple (login, password).
212 * if the host is an ipv6 address, brackets will be added to surround it.
218 netloc
= urllib
.quote(str(auth
[0])) # Login
220 netloc
+= ':%s' % urllib
.quote(auth
[1]) # Password
223 # host is an ipv6 address whithout brackets
224 if ':' in host
and not re
.match(r
'^\[.*\]$', host
):
225 netloc
+= '[%s]' % host
230 netloc
+= ':%s' % port
232 url
= urlparse
.urlunparse((scheme
, netloc
, path
, params
, query
, fragment
))
236 def setLocationOption(self
):
237 if not self
.options
.get('location'):
238 self
.options
['location'] = os
.path
.join(
239 self
.buildout
['buildout']['parts-directory'], self
.name
)
241 def download(self
, destination
=None):
242 """ A simple wrapper around h.r.download, downloading to self.location"""
243 self
.setLocationOption()
245 import hexagonit
.recipe
.download
247 destination
= self
.location
248 if os
.path
.exists(destination
):
249 # leftovers from a previous failed attempt, removing it.
250 log
.warning('Removing already existing directory %s' % destination
)
251 shutil
.rmtree(destination
)
252 os
.mkdir(destination
)
255 options
= self
.options
.copy()
256 options
['destination'] = destination
257 hexagonit
.recipe
.download
.Recipe(
258 self
.buildout
, self
.name
, options
).install()
260 shutil
.rmtree(destination
)