Package gavo :: Package base :: Module osinter
[frames] | no frames]

Source Code for Module gavo.base.osinter

  1  """ 
  2  Basic OS interface/utility functions that depend on our configuration. 
  3   
  4  (everything that doesn't need getConfig is somewhere in gavo.utils) 
  5  """ 
  6   
  7  #c Copyright 2008-2019, the GAVO project 
  8  #c 
  9  #c This program is free software, covered by the GNU GPL.  See the 
 10  #c COPYING file in the source distribution. 
 11   
 12   
 13  import grp 
 14  import inspect 
 15  import re 
 16  import os 
 17  import subprocess 
 18  import time 
 19  import urllib 
 20  import urlparse 
 21  from email import charset 
 22  from email import utils as emailutils 
 23  from email.header import Header 
 24  from email.parser import Parser 
 25  from email.mime.nonmultipart import MIMENonMultipart 
 26   
 27   
 28  import pkg_resources 
 29   
 30  from gavo.base import config 
 31  from gavo import utils 
32 33 34 -def getGroupId():
35 gavoGroup = config.get("group") 36 try: 37 return grp.getgrnam(gavoGroup)[2] 38 except KeyError as ex: 39 raise utils.ReportableError("Group %s does not exist"%str(ex), 40 hint="You should have created this (unix) group when you" 41 " created the server user (usually, 'gavo'). Just do it" 42 " now and re-run this program.")
43
44 45 -def makeSharedDir(path, writable=True):
46 """creates a directory with group ownership [general]group. 47 48 There's much that can to wrong; we try to raise useful error messages. 49 """ 50 if not os.path.isdir(path): 51 try: 52 os.makedirs(path) 53 except os.error as err: 54 raise utils.ReportableError( 55 "Could not create directory %s"%path, 56 hint="The operating system reported: %s"%err) 57 except Exception as msg: 58 raise utils.ReportableError( 59 "Could not create directory %s (%s)"%(path, msg)) 60 61 gavoGroup = getGroupId() 62 stats = os.stat(path) 63 if stats.st_mode&0060!=060 or stats.st_gid!=gavoGroup: 64 try: 65 os.chown(path, -1, gavoGroup) 66 if writable: 67 os.chmod(path, stats.st_mode | 0060) 68 except Exception as msg: 69 raise utils.ReportableError( 70 "Cannot set %s to group ownership %s, group writable"%( 71 path, gavoGroup), 72 hint="Certain directories must be writable by multiple user ids." 73 " They must therefore belong to the group %s and be group" 74 " writeable. The attempt to make sure that's so just failed" 75 " with the error message %s." 76 " Either grant the directory in question to yourself, or" 77 " fix permissions manually. If you own the directory and" 78 " sill see permission errors, try 'newgrp %s'"%( 79 config.get("group"), msg, config.get("group")))
80
81 82 @utils.memoized 83 -def getHTTPBase():
84 """returns the server's base URL for the http protocol. 85 86 This is just serverURL from the configuration, unless serverURL is https; 87 in that case, we replace https with http. serverPort is ignored here 88 under the assumption that there's a reverse proxy. If that bites you, 89 we could introduce an alternativeServerURL config item. 90 """ 91 serverURL = config.get("web", "serverurl") 92 if serverURL.startswith("https:"): 93 return "http:"+serverURL[6:] 94 else: 95 return serverURL
96
97 98 @utils.memoized 99 -def getHTTPSBase():
100 """return the server's base URL for the https protocol. 101 102 If serverURL already is https, that's what's returned. If not, the URL is 103 parsed, any port specification is removed (i.e., we only support https on 104 port 443), the protocol is changed to https, and the result is returned. 105 """ 106 serverURL = config.get("web", "serverurl") 107 if serverURL.startswith("https:"): 108 return serverURL 109 else: 110 parts = urlparse.urlparse(serverURL) 111 return urlparse.urlunparse(("https", parts.hostname, parts.path, 112 parts.params, parts.query, parts.fragment))
113
114 115 -def switchProtocol(url):
116 """tries to make an https URL from an http one and vice versa. 117 118 This function will raise a ValueError if url doesn't start with either 119 HTTPBase or HTTPSBase. Otherwise, it will replace one by the other. 120 """ 121 httpBase = getHTTPBase() 122 httpsBase = getHTTPSBase() 123 124 if url.startswith(httpBase): 125 return httpsBase+url[len(httpBase):] 126 elif url.startswith(httpsBase): 127 return httpBase+url[len(httpsBase):] 128 else: 129 raise ValueError("Cannot switch protocol on a URL not configured" 130 " in [web]serverURL")
131
132 133 -def getCurrentServerURL():
134 """returns the server URL pertinent for the current request. 135 136 This looks upstack for a renderer object having a HANDLING_HTTPS attribute. 137 If it finds one, it will return HTTPBase() or HTTPSBase() as appropriate. 138 If not, it will return [web]serverurl 139 """ 140 frame = inspect.currentframe().f_back.f_back 141 while frame: 142 if "self" in frame.f_locals: 143 if hasattr(frame.f_locals["self"], "HANDLING_HTTPS"): 144 if frame.f_locals["self"].HANDLING_HTTPS: 145 return getHTTPSBase() 146 else: 147 return getHTTPBase() 148 break 149 frame = frame.f_back 150 151 return config.get("web", "serverurl")
152
153 154 @utils.document 155 -def makeSitePath(path):
156 """returns a rooted local part for a server-internal URL. 157 158 uri itself needs to be server-absolute; a leading slash is recommended 159 for clarity but not mandatory. 160 """ 161 return str(config.get("web", "nevowRoot")+path.lstrip("/"))
162
163 164 @utils.document 165 -def makeAbsoluteURL(path, canonical=False):
166 """returns a fully qualified URL for a rooted local part. 167 168 This will reflect the http/https access mode unless you pass 169 canonical=True, in which case [web]serverURL will be used unconditionally. 170 """ 171 if canonical: 172 serverURL = config.get("web", "serverurl") 173 else: 174 serverURL = getCurrentServerURL() 175 return str(serverURL+makeSitePath(path))
176
177 178 -def getBinaryName(baseName):
179 """returns the name of a binary it thinks is appropriate for the platform. 180 181 To do this, it asks config for the platform name, sees if there's a binary 182 <bin>-<platname> if platform is nonempty. If it exists, it returns that name, 183 in all other cases, it returns baseName unchanged. 184 """ 185 platform = config.get("platform") 186 if platform: 187 platName = baseName+"-"+platform 188 if os.path.exists(platName): 189 return platName 190 return baseName
191
192 193 -def getPathForDistFile(name):
194 """returns a path for a "dist resource", i.e., a file distributed 195 with DaCHS. 196 197 name is the file relative to resources. 198 199 This is essentially pkg_resources.resource_filename with a dash 200 of built-in configuration. 201 """ 202 return pkg_resources.resource_filename('gavo', "resources/"+name)
203
204 205 -def openDistFile(name):
206 """returns an open file for a "dist resource", i.e., a file distributed 207 with DaCHS. 208 209 see getPathForDistFile 210 """ 211 return open(getPathForDistFile(name))
212
213 214 -def getVersion():
215 """returns (as a string) the DaCHS version running. 216 217 The information is obtained from setuptools. 218 """ 219 return pkg_resources.require("gavodachs")[0].version
220
221 222 -def formatMail(mailText):
223 """returns a mail with headers and content properly formatted as 224 a bytestring and MIME. 225 226 mailText must be a unicode instance or pure ASCII 227 """ 228 rawHeaders, rawBody = mailText.split("\n\n", 1) 229 cs = charset.Charset("utf-8") 230 cs.body_encoding = charset.QP 231 cs.header_encoding = charset.QP 232 # they've botched MIMEText so bad it can't really generate 233 # quoted-printable UTF-8 any more. So, let's forget MIMEText: 234 msg = MIMENonMultipart("text", "plain", charset="utf-8") 235 msg.set_payload(rawBody, charset=cs) 236 237 for key, value in Parser().parsestr(rawHeaders.encode("utf-8")).items(): 238 if re.match("[ -~]*$", value): 239 # it's plain ASCII, don't needlessly uglify output 240 msg[key] = value 241 else: 242 msg[key] = Header(value, cs) 243 msg["Date"] = emailutils.formatdate(time.time(), 244 localtime=False, usegmt=True) 245 msg["X-Mailer"] = "DaCHS VO Server" 246 return msg.as_string()
247
248 249 -def sendMail(mailText):
250 """sends mailText (which has to have all the headers) via sendmail. 251 252 (which is configured in [general]sendmail). 253 254 This will return True when sendmail has accepted the mail, False 255 otherwise. 256 """ 257 if not config.get("sendmail"): 258 utils.setUIEvent("Warning", "Wanted to send maintainer mail but" 259 " could not since [general]sendmail is not configured.") 260 261 mailText = formatMail(mailText) 262 263 pipe = subprocess.Popen(config.get("sendmail"), shell=True, 264 stdin=subprocess.PIPE) 265 pipe.stdin.write(mailText) 266 pipe.stdin.close() 267 268 if pipe.wait(): 269 utils.sendUIEvent("Error", "Wanted to send mail starting with" 270 " '%s', but sendmail returned an error message" 271 " (check the [general]sendmail setting)."% 272 utils.makeEllipsis(mailText, 300)) 273 return False 274 275 return True
276
277 278 -def tryRemoteReload(rdId):
279 """tries to reload the rdId on a running service 280 281 This only works if there's [web]adminpasswd and[web]serverURL 282 set, and both match what the actual server uses. 283 """ 284 pw = config.get("web", "adminpasswd") 285 # don't bother if admin passwd has not been set or when running unit tests. 286 if pw=="" or pw=="this_is_the_unittest_suite": 287 return 288 289 try: 290 f = utils.urlopenRemote(makeAbsoluteURL("/seffe/%s"%rdId), 291 urllib.urlencode({"__nevow_form__": "adminOps", "submit": "Reload RD"}), 292 creds=("gavoadmin", pw)) 293 f.read() 294 except IOError as ex: 295 utils.sendUIEvent("Debug", "Could not reload %s RD (%s). This means" 296 " that the server may still use stale metadata. You may want" 297 " to reload %s manually (or restart the server)."%(rdId, ex, rdId))
298