5 # The contents of this file are subject to the terms of the
6 # Common Development and Distribution License (the "License").
7 # You may not use this file except in compliance with the License.
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 # or http://www.opensolaris.org/os/licensing.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
14 # When distributing Covered Code, include this CDDL HEADER in each
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 # If applicable, add the following below this CDDL HEADER, with the
17 # fields enclosed by brackets "[]" replaced with your own identifying
18 # information: Portions Copyright [yyyy] [name of copyright owner]
24 # Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
43 from mako
.template
import Template
45 from pkg
.client
import global_settings
46 from pkg
.misc
import msg
, PipeError
51 import pkg
.client
.progress
as progress
52 import pkg
.client
.api_errors
as apx
53 import pkg
.digest
as digest
54 import pkg
.misc
as misc
55 import pkg
.portable
as portable
58 logger
= global_settings
.logger
61 PKG_CLIENT_NAME
= "pkg.sysrepo"
62 CLIENT_API_VERSION
= 82
63 pkg
.client
.global_settings
.client_name
= PKG_CLIENT_NAME
71 # This is a simple python script, run from the method script that starts
72 # svc:/application/pkg/system-repository:default.
74 # It writes an Apache configuration that is used to serve responses to pkg
75 # clients querying the system repository, as well as providing http/https proxy
76 # services to those clients, accessing external repositories.
77 # file:// repositories on the system running the system repository are also
78 # exposed to pkg clients, via Alias directives.
80 # See src/util/apache2/sysrepo/*.mako for the templates used to create the
81 # Apache configuration.
83 # The following filesystem locations are used:
85 # variable default install path description
86 # --------- --------------------- ------------
87 # runtime_dir system/volatile/pkg/sysrepo runtime .conf, htdocs, pid files
88 # template_dir etc/pkg/sysrepo mako templates
89 # log_dir var/log/pkg/sysrepo log files
90 # cache_dir var/cache/pkg/sysrepo apache proxy cache
92 # all of the above can be modified with command line arguments.
95 SYSREPO_CRYPTO_FILENAME
= "crypto.txt"
96 SYSREPO_HTTP_TEMPLATE
= "sysrepo_httpd.conf.mako"
97 SYSREPO_HTTP_FILENAME
= "sysrepo_httpd.conf"
99 SYSREPO_PUB_TEMPLATE
= "sysrepo_publisher_response.mako"
100 SYSREPO_PUB_FILENAME
= "index.html"
102 SYSREPO_HTDOCS_DIRNAME
= "htdocs"
104 SYSREPO_VERSIONS_DIRNAME
= ["versions", "0"]
105 SYSREPO_SYSPUB_DIRNAME
= ["syspub", "0"]
106 SYSREPO_PUB_DIRNAME
= ["publisher", "0"]
108 # static string with our versions response
109 SYSREPO_VERSIONS_STR
= """\
117 """.format(pkg
.VERSION
)
119 SYSREPO_USER
= "pkg5srv"
120 SYSREPO_GROUP
= "pkg5srv"
122 class SysrepoException(Exception):
123 def __unicode__(self
):
124 # To workaround python issues 6108 and 2517, this provides a
125 # a standard wrapper for this class' exceptions so that they
126 # have a chance of being stringified correctly.
131 """To be called at program finish."""
134 def error(text
, cmd
=None):
135 """Emit an error message prefixed by the command name """
138 text
= "{0}: {1}".format(cmd
, text
)
139 pkg_cmd
= "pkg.sysrepo "
141 pkg_cmd
= "pkg.sysrepo: "
143 # If we get passed something like an Exception, we can convert
144 # it down to a string.
147 # If the message starts with whitespace, assume that it should come
148 # *before* the command-name prefix.
149 text_nows
= text
.lstrip()
150 ws
= text
[:len(text
) - len(text_nows
)]
152 # This has to be a constant value as we can't reliably get our actual
153 # program name on all platforms.
154 logger
.error(ws
+ pkg_cmd
+ text_nows
)
156 def usage(usage_error
=None, cmd
=None, retcode
=EXIT_BADOPT
):
157 """Emit a usage message and optionally prefix it with a more
158 specific error message. Causes program to exit.
162 error(usage_error
, cmd
=cmd
)
166 pkg.sysrepo -p <port> [-R image_root] [ -c cache_dir] [-h hostname]
167 [-l logs_dir] [-r runtime_dir] [-s cache_size] [-t template_dir]
168 [-T http_timeout] [-w http_proxy] [-W https_proxy]
172 def _get_image(image_dir
):
173 """Return a pkg.client.api.ImageInterface for the provided
180 tracker
= progress
.QuietProgressTracker()
182 api_inst
= pkg
.client
.api
.ImageInterface(
183 image_dir
, CLIENT_API_VERSION
,
184 tracker
, None, PKG_CLIENT_NAME
)
186 if api_inst
.root
!= image_dir
:
187 msg(_("Problem getting image at {0}").format(
189 except Exception as err
:
190 raise SysrepoException(
191 _("Unable to get image at {dir}: {reason}").format(
195 # restore the current directory, which ImageInterace had changed
199 def _follow_redirects(uri_list
, http_timeout
):
200 """ Follow HTTP redirects from servers. Needed so that we can create
201 RewriteRules for all repository URLs that pkg clients may encounter.
203 We return a sorted list of URIs that were found having followed all
204 redirects in 'uri_list'. We also return a boolean, True if we timed out
205 when following any of the URIs.
208 ret_uris
= set(uri_list
)
211 class SysrepoRedirectHandler(urllib2
.HTTPRedirectHandler
):
212 """ A HTTPRedirectHandler that saves URIs we've been
213 redirected to along the path to our eventual destination."""
215 self
.redirects
= set()
217 def redirect_request(self
, req
, fp
, code
, msg
, hdrs
, newurl
):
218 self
.redirects
.add(newurl
)
219 return urllib2
.HTTPRedirectHandler
.redirect_request(
220 self
, req
, fp
, code
, msg
, hdrs
, newurl
)
223 handler
= SysrepoRedirectHandler()
224 opener
= urllib2
.build_opener(handler
)
225 if not uri
.startswith("http:"):
226 ret_uris
.update([uri
])
229 # otherwise, open a known url to check for redirects
231 opener
.open("{0}/versions/0".format(uri
), None,
234 [item
.replace("/versions/0", "").rstrip("/")
235 for item
in handler
.redirects
]))
236 except urllib2
.URLError
as err
:
237 # We need to log this, and carry on - the url
238 # could become available at a later date.
239 msg(_("WARNING: unable to access {uri} when checking "
240 "for redirects: {err}").format(**locals()))
243 return sorted(list(ret_uris
)), timed_out
245 def __validate_pub_info(pub_info
, no_uri_pubs
, api_inst
):
246 """Determine if pub_info and no_uri_pubs objects, which may have been
247 decoded from a json representation are valid, raising a SysrepoException
250 We use the api_inst to sanity-check that all publishers configured in
251 the image are represented in pub_info or no_uri_pubs, and that their
254 SysrepoExceptions are raised with developer-oriented debug messages
255 which are not to be translated or shown to users.
258 # validate the structure of the pub_info object
259 if not isinstance(pub_info
, dict):
260 raise SysrepoException("{0} is not a dict".format(pub_info
))
262 if not isinstance(uri
, basestring
):
263 raise SysrepoException("{0} is not a basestring".format(
265 uri_info
= pub_info
[uri
]
266 if not isinstance(uri_info
, list):
267 raise SysrepoException("{0} is not a list".format(
269 for props
in uri_info
:
271 raise SysrepoException("{0} does not have 6 "
272 "items".format(props
))
273 # props [0] and [3] must be strings
274 if not isinstance(props
[0], basestring
) or \
275 not isinstance(props
[3], basestring
):
276 raise SysrepoException("indices 0 and 3 of {0} "
277 "are not basestrings".format(props
))
278 # prop[5] must be a string, either "file" or "dir"
279 # and prop[0] must start with file://
280 if not isinstance(props
[5], basestring
) or \
281 (props
[5] not in ["file", "dir"] and
282 props
[0].startswith("file://")):
283 raise SysrepoException("index 5 of {0} is not a "
284 "basestring or is not 'file' or 'dir'".format(
286 # validate the structure of the no_uri_pubs object
287 if not isinstance(no_uri_pubs
, list):
288 raise SysrepoException("{0} is not a list".format(no_uri_pubs
))
289 for item
in no_uri_pubs
:
290 if not isinstance(item
, basestring
):
291 raise SysrepoException(
292 "{0} is not a basestring".format(item
))
294 # check that we have entries for each URI for each publisher.
295 # (we may have more URIs than these, due to server-side http redirects
296 # that are not reflected as origins or mirrors in the image itself)
297 for pub
in api_inst
.get_publishers():
300 repo
= pub
.repository
301 for uri
in repo
.mirrors
+ repo
.origins
:
302 uri_key
= uri
.uri
.rstrip("/")
303 if uri_key
not in pub_info
:
304 raise SysrepoException("{0} is not in {1}".format(
306 if repo
.mirrors
+ repo
.origins
== []:
307 if pub
.prefix
not in no_uri_pubs
:
308 raise SysrepoException("{0} is not in {1}".format(
309 pub
.prefix
, no_uri_pubs
))
312 def _load_publisher_info(api_inst
, image_dir
):
313 """Loads information about the publishers configured for the
314 given ImageInterface from image_dir in a format identical to that
315 returned by _get_publisher_info(..) that is, a dictionary mapping
316 URIs to a list of lists. An example entry might be:
317 pub_info[uri] = [[prefix, cert, key, hash of the uri, proxy], ... ]
319 and a list of publishers which have no origin or mirror URIs.
321 If the cache doesn't exist, or is in a format we don't recognise, or
322 we've managed to determine that it's stale, we return None, None
323 indicating that the publisher_info must be rebuilt.
327 cache_path
= os
.path
.join(image_dir
,
328 pkg
.client
.global_settings
.sysrepo_pub_cache_path
)
331 st_cache
= os
.lstat(cache_path
)
333 if e
.errno
== errno
.ENOENT
:
338 # the cache must be a regular file
339 if not stat
.S_ISREG(st_cache
.st_mode
):
340 raise IOError("not a regular file")
342 with
open(cache_path
, "r") as cache_file
:
344 pub_info_tuple
= simplejson
.load(cache_file
)
345 except simplejson
.JSONDecodeError
:
346 error(_("Invalid config cache file at {0} "
347 "generating fresh configuration.").format(
351 if len(pub_info_tuple
) != 2:
352 error(_("Invalid config cache at {0} "
353 "generating fresh configuration.").format(
357 pub_info
, no_uri_pubs
= pub_info_tuple
358 # sanity-check the cached configuration
360 __validate_pub_info(pub_info
, no_uri_pubs
,
362 except SysrepoException
as e
:
363 error(_("Invalid config cache at {0} "
364 "generating fresh configuration.").format(
368 # If we have any problems loading the publisher info, we explain why.
370 error(_("Unable to load config from {cache_path}: {e}").format(
374 return pub_info
, no_uri_pubs
376 def _store_publisher_info(uri_pub_map
, no_uri_pubs
, image_dir
):
377 """Stores a given pair of (uri_pub_map, no_uri_pubs) objects to a
378 configuration cache file beneath image_dir."""
379 cache_path
= os
.path
.join(image_dir
,
380 pkg
.client
.global_settings
.sysrepo_pub_cache_path
)
381 cache_dir
= os
.path
.dirname(cache_path
)
383 if not os
.path
.exists(cache_dir
):
384 os
.makedirs(cache_dir
, 0o700)
386 # if the cache exists, it must be a file
387 st_cache
= os
.lstat(cache_path
)
388 if not stat
.S_ISREG(st_cache
.st_mode
):
389 raise IOError("not a regular file")
393 with
open(cache_path
, "wb") as cache_file
:
394 simplejson
.dump((uri_pub_map
, no_uri_pubs
), cache_file
,
396 os
.chmod(cache_path
, 0o600)
398 error(_("Unable to store config to {cache_path}: {e}").format(
401 def _valid_proxy(proxy
):
402 """Checks the given proxy string to make sure that it does not contain
403 any authentication details since these are not supported by ProxyRemote.
406 u
= urllib2
.urlparse
.urlparse(proxy
)
407 netloc_parts
= u
.netloc
.split("@")
408 # If we don't have any authentication details, return.
409 if len(netloc_parts
) == 1:
413 def _get_publisher_info(api_inst
, http_timeout
, image_dir
):
414 """Returns information about the publishers configured for the given
417 The first item returned is a map of uris to a list of lists of the form
418 [[prefix, cert, key, hash of the uri, proxy, uri type], ... ]
420 The second item returned is a list of publisher prefixes which specify
423 Where possible, we attempt to load cached publisher information, but if
424 that cached information is stale or unavailable, we fall back to
425 querying the image for the publisher information, verifying repository
426 URIs and checking for redirects and write that information to the
429 # the cache gets deleted by pkg.client.image.Image.save_config()
430 # any time publisher configuration changes are made.
431 uri_pub_map
, no_uri_pubs
= _load_publisher_info(api_inst
, image_dir
)
433 return uri_pub_map
, no_uri_pubs
435 # map URIs to (pub.prefix, cert, key, hash, proxy, utype) tuples
440 for pub
in api_inst
.get_publishers():
445 repo
= pub
.repository
447 # Determine the proxies to use per URI
449 for uri
in repo
.mirrors
+ repo
.origins
:
450 key
= uri
.uri
.rstrip("/")
452 # Apache can only use a single proxy, even
453 # if many are configured. Use the first we find.
454 proxy_map
[key
] = uri
.proxies
[0].uri
456 # Apache's ProxyRemote directive does not allow proxies that
457 # require authentication.
458 for uri
in proxy_map
:
459 if not _valid_proxy(proxy_map
[uri
]):
460 raise SysrepoException("proxy value {val} "
461 "for {uri} is not supported.".format(
462 uri
=uri
, val
=proxy_map
[uri
]))
464 uri_list
, timed_out
= _follow_redirects(
465 [repo_uri
.uri
.rstrip("/")
466 for repo_uri
in repo
.mirrors
+ repo
.origins
],
471 # We keep a field to store information about the type
472 # of URI we're looking at, which saves us
473 # from needing to make os.path.isdir(..) or
474 # os.path.isfile(..) calls when processing the template.
475 # This is important when we're rebuilding the
476 # configuration from cached publisher info and an
477 # file:// repository is temporarily unreachable.
479 if uri
.startswith("file:"):
480 # we only support p5p files and directory-based
481 # repositories of >= version 4.
482 urlresult
= urllib2
.urlparse
.urlparse(uri
)
484 if not os
.path
.exists(urlresult
.path
):
485 raise SysrepoException(
486 _("file repository {0} does not "
487 "exist or is not accessible").format(uri
))
488 if os
.path
.isdir(urlresult
.path
) and \
489 not os
.path
.exists(os
.path
.join(
490 urlresult
.path
, "pkg5.repository")):
491 raise SysrepoException(
492 _("file repository {0} cannot be "
493 "proxied. Only file "
494 "repositories of version 4 or "
495 "later are supported.").format(uri
))
496 if not os
.path
.isdir(urlresult
.path
):
499 p5p
.Archive(urlresult
.path
)
500 except p5p
.InvalidArchive
:
501 raise SysrepoException(
502 _("unable to read p5p "
503 "archive file at {0}").format(
506 hash = _uri_hash(uri
)
507 # we don't have per-uri ssl key/cert information yet,
508 # so we just pull it from one of the RepositoryURIs.
509 cert
= repo_uri
.ssl_cert
510 key
= repo_uri
.ssl_key
511 uri_pub_map
.setdefault(uri
, []).append(
512 (prefix
, cert
, key
, hash, proxy_map
.get(uri
), utype
)
515 if not repo
.mirrors
+ repo
.origins
:
516 no_uri_pubs
.append(prefix
)
518 # if we weren't able to follow all redirects, then we don't write a new
519 # cache, because it could be incomplete.
521 _store_publisher_info(uri_pub_map
, no_uri_pubs
, image_dir
)
522 return uri_pub_map
, no_uri_pubs
524 def _chown_cache_dir(dir):
525 """Sets ownership for cache directory as pkg5srv:bin"""
527 uid
= portable
.get_user_by_name(SYSREPO_USER
, None, False)
528 gid
= portable
.get_group_by_name("bin", None, False)
530 os
.chown(dir, uid
, gid
)
531 except OSError as err
:
532 if not os
.environ
.get("PKG5_TEST_ENV", None):
533 raise SysrepoException(
534 _("Unable to chown to {user}:{group}: "
536 user
=SYSREPO_USER
, group
="bin",
539 def _write_httpd_conf(runtime_dir
, log_dir
, template_dir
, host
, port
, cache_dir
,
540 cache_size
, uri_pub_map
, http_proxy
, https_proxy
):
541 """Writes the apache configuration for the system repository.
543 If http_proxy or http_proxy is supplied, it will override any proxy
544 values set in the image we're reading configuration from.
549 socket
.gethostbyname(host
)
551 # check our directories
552 dirs
= [runtime_dir
, log_dir
]
553 if cache_dir
not in ["None", "memory"]:
554 dirs
.append(cache_dir
)
555 for dir in dirs
+ [template_dir
]:
556 if os
.path
.exists(dir) and not os
.path
.isdir(dir):
557 raise SysrepoException(
558 _("{0} is not a directory").format(dir))
562 os
.makedirs(dir, 0o755)
563 # set pkg5srv:bin as ownership for cache
566 _chown_cache_dir(dir)
567 except OSError as err
:
568 if err
.errno
!= errno
.EEXIST
:
574 if num
<= 0 or num
>= 65535:
575 raise SysrepoException(
576 _("invalid port: {0}").format(port
))
578 raise SysrepoException(_("invalid port: {0}").format(
581 # check our cache size
583 num
= int(cache_size
)
585 raise SysrepoException(_("invalid cache size: "
588 raise SysrepoException(
589 _("invalid cache size: {0}").format(cache_size
))
591 # check our proxy arguments - we can use a proxy to handle
592 # incoming http or https requests, but that proxy must use http.
593 for key
, val
in [("http_proxy", http_proxy
),
594 ("https_proxy", https_proxy
)]:
598 result
= urllib2
.urlparse
.urlparse(val
)
599 if result
.scheme
!= "http":
601 _("scheme must be http"))
602 if not result
.netloc
:
603 raise Exception("missing netloc")
604 if not _valid_proxy(val
):
605 raise Exception("unsupported proxy")
606 except Exception as e
:
607 raise SysrepoException(
608 _("invalid {key}: {val}: {err}").format(
609 key
=key
, val
=val
, err
=str(e
)))
611 httpd_conf_template_path
= os
.path
.join(template_dir
,
612 SYSREPO_HTTP_TEMPLATE
)
614 # we're disabling unicode here because we want Mako to
615 # passthrough any filesystem path names, whatever the
617 httpd_conf_template
= Template(
618 filename
=httpd_conf_template_path
,
619 disable_unicode
=True)
621 # our template expects cache size expressed in Kb
622 httpd_conf_text
= httpd_conf_template
.render(
623 sysrepo_log_dir
=log_dir
,
624 sysrepo_runtime_dir
=runtime_dir
,
625 sysrepo_template_dir
=template_dir
,
626 uri_pub_map
=uri_pub_map
,
631 cache_size
=int(cache_size
) * 1024,
632 http_proxy
=http_proxy
,
633 https_proxy
=https_proxy
)
634 httpd_conf_path
= os
.path
.join(runtime_dir
,
635 SYSREPO_HTTP_FILENAME
)
636 httpd_conf_file
= file(httpd_conf_path
, "wb")
637 httpd_conf_file
.write(httpd_conf_text
)
638 httpd_conf_file
.close()
639 except socket
.gaierror
as err
:
640 raise SysrepoException(
641 _("Unable to write sysrepo_httpd.conf: {host}: "
642 "{err}").format(**locals()))
643 except (OSError, IOError) as err
:
644 raise SysrepoException(
645 _("Unable to write sysrepo_httpd.conf: {0}").format(err
))
647 def _write_crypto_conf(runtime_dir
, uri_pub_map
):
648 """Writes the crypto.txt file, containing keys and certificates
649 in order for the system repository to proxy to https repositories."""
652 crypto_path
= os
.path
.join(runtime_dir
, SYSREPO_CRYPTO_FILENAME
)
653 file(crypto_path
, "w").close()
654 os
.chmod(crypto_path
, 0o600)
655 written_crypto_content
= False
657 for repo_list
in uri_pub_map
.values():
658 for (pub
, cert_path
, key_path
, hash, proxy
, utype
) in \
660 if cert_path
and key_path
:
661 crypto_file
= file(crypto_path
, "a")
662 crypto_file
.writelines(file(cert_path
))
663 crypto_file
.writelines(file(key_path
))
665 written_crypto_content
= True
667 # Apache needs us to have some content in this file
668 if not written_crypto_content
:
669 crypto_file
= file(crypto_path
, "w")
671 "# this space intentionally left blank\n")
673 os
.chmod(crypto_path
, 0o400)
674 except OSError as err
:
675 raise SysrepoException(
676 _("unable to write crypto.txt file: {0}").format(err
))
678 def _write_publisher_response(uri_pub_map
, htdocs_path
, template_dir
):
679 """Writes static html for all file-repository-based publishers that
680 is served as their publisher/0 responses. Responses for
681 non-file-based publishers are handled by rewrite rules in our
682 Apache configuration."""
685 # build a version of our uri_pub_map, keyed by publisher
687 for uri
in uri_pub_map
:
688 for (pub
, cert
, key
, hash, proxy
, utype
) in \
690 if pub
not in pub_uri_map
:
691 pub_uri_map
[pub
] = []
692 pub_uri_map
[pub
].append(
693 (uri
, cert
, key
, hash, proxy
, utype
))
695 publisher_template_path
= os
.path
.join(template_dir
,
696 SYSREPO_PUB_TEMPLATE
)
697 publisher_template
= Template(filename
=publisher_template_path
)
699 for pub
in pub_uri_map
:
700 for (uri
, cert_path
, key_path
, hash, proxy
, utype
) in \
702 if uri
.startswith("file:"):
704 publisher_template
.render(
706 publisher_path
= os
.path
.sep
.join(
707 [htdocs_path
, pub
, hash] +
709 os
.makedirs(publisher_path
)
710 publisher_file
= file(
711 os
.path
.sep
.join([publisher_path
,
712 SYSREPO_PUB_FILENAME
]), "w")
713 publisher_file
.write(publisher_text
)
714 publisher_file
.close()
715 except OSError as err
:
716 raise SysrepoException(
717 _("unable to write publisher response: {0}").format(err
))
719 def _write_versions_response(htdocs_path
):
720 """Writes a static versions/0 response for the system repository."""
723 versions_path
= os
.path
.join(htdocs_path
,
724 os
.path
.sep
.join(SYSREPO_VERSIONS_DIRNAME
))
725 os
.makedirs(versions_path
)
727 versions_file
= file(os
.path
.join(versions_path
, "index.html"),
729 versions_file
.write(SYSREPO_VERSIONS_STR
)
730 versions_file
.close()
731 except OSError as err
:
732 raise SysrepoException(
733 _("Unable to write versions response: {0}").format(err
))
735 def _write_sysrepo_response(api_inst
, htdocs_path
, uri_pub_map
, no_uri_pubs
):
736 """Writes a static syspub/0 response for the system repository."""
739 sysrepo_path
= os
.path
.join(htdocs_path
,
740 os
.path
.sep
.join(SYSREPO_SYSPUB_DIRNAME
))
741 os
.makedirs(sysrepo_path
)
744 for uri
in uri_pub_map
.keys()
745 for info
in uri_pub_map
[uri
]
747 pub_prefixes
.extend(no_uri_pubs
)
748 api_inst
.write_syspub(os
.path
.join(sysrepo_path
, "index.html"),
750 except (OSError, apx
.ApiException
) as err
:
751 raise SysrepoException(
752 _("Unable to write syspub response: {0}").format(err
))
755 """Returns a string hash of the given URI"""
756 return digest
.DEFAULT_HASH_FUNC(uri
).hexdigest()
758 def _chown_runtime_dir(runtime_dir
):
759 """Change the ownership of all files under runtime_dir to our sysrepo
762 uid
= portable
.get_user_by_name(SYSREPO_USER
, None, False)
763 gid
= portable
.get_group_by_name(SYSREPO_GROUP
, None, False)
765 misc
.recursive_chown_dir(runtime_dir
, uid
, gid
)
766 except OSError as err
:
767 if not os
.environ
.get("PKG5_TEST_ENV", None):
768 raise SysrepoException(
769 _("Unable to chown to {user}:{group}: "
771 user
=SYSREPO_USER
, group
=SYSREPO_GROUP
,
774 def cleanup_conf(runtime_dir
=None):
775 """Destroys an old configuration."""
777 shutil
.rmtree(runtime_dir
, ignore_errors
=True)
778 except OSError as err
:
779 raise SysrepoException(
780 _("Unable to cleanup old configuration: {0}").format(err
))
782 def refresh_conf(image_root
="/", port
=None, runtime_dir
=None,
783 log_dir
=None, template_dir
=None, host
="127.0.0.1", cache_dir
=None,
784 cache_size
=1024, http_timeout
=3, http_proxy
=None, https_proxy
=None):
785 """Creates a new configuration for the system repository.
787 TODO: a way to map only given zones to given publishers
791 cleanup_conf(runtime_dir
=runtime_dir
)
793 http_timeout
= int(http_timeout
)
794 except ValueError as err
:
795 raise SysrepoException(
796 _("invalid value for http_timeout: {0}").format(err
))
798 raise SysrepoException(
799 _("http_timeout must a positive integer"))
801 api_inst
= _get_image(image_root
)
802 uri_pub_map
, no_uri_pubs
= _get_publisher_info(api_inst
,
803 http_timeout
, api_inst
.root
)
804 except SysrepoException
as err
:
805 raise SysrepoException(
806 _("unable to get publisher information: {0}").format(
809 htdocs_path
= os
.path
.join(runtime_dir
,
810 SYSREPO_HTDOCS_DIRNAME
)
811 os
.makedirs(htdocs_path
)
812 except OSError as err
:
813 raise SysrepoException(
814 _("unable to create htdocs dir: {0}").format(err
))
816 _write_httpd_conf(runtime_dir
, log_dir
, template_dir
, host
,
817 port
, cache_dir
, cache_size
, uri_pub_map
, http_proxy
,
819 _write_crypto_conf(runtime_dir
, uri_pub_map
)
820 _write_publisher_response(uri_pub_map
, htdocs_path
,
822 _write_versions_response(htdocs_path
)
823 _write_sysrepo_response(api_inst
, htdocs_path
, uri_pub_map
,
825 _chown_runtime_dir(runtime_dir
)
826 except SysrepoException
as err
:
832 global_settings
.client_name
= PKG_CLIENT_NAME
837 orig_cwd
= os
.getcwd()
840 orig_cwd
= os
.environ
["PWD"]
841 if not orig_cwd
or orig_cwd
[0] != "/":
846 # some sensible defaults
849 # an empty image_root means we don't get '//' in the below
850 # _get_image() deals with "" in a sane manner.
852 cache_dir
= "{0}/var/cache/pkg/sysrepo".format(image_root
)
854 template_dir
= "{0}/etc/pkg/sysrepo".format(image_root
)
855 runtime_dir
= "{0}/var/run/pkg/sysrepo".format(image_root
)
856 log_dir
= "{0}/var/log/pkg/sysrepo".format(image_root
)
862 opts
, pargs
= getopt
.getopt(sys
.argv
[1:],
863 "c:h:l:p:r:R:s:t:T:w:W:?", ["help"])
864 for opt
, arg
in opts
:
890 except getopt
.GetoptError
as e
:
891 usage(_("illegal global option -- {0}").format(e
.opt
))
894 usage(_("required port option missing."))
896 ret
= refresh_conf(image_root
=image_root
, log_dir
=log_dir
,
897 host
=host
, port
=port
, runtime_dir
=runtime_dir
,
898 template_dir
=template_dir
, cache_dir
=cache_dir
,
899 cache_size
=cache_size
, http_timeout
=http_timeout
,
900 http_proxy
=http_proxy
, https_proxy
=https_proxy
)
904 # Establish a specific exit status which means: "python barfed an exception"
905 # so that we can more easily detect these in testing of the CLI commands.
907 def handle_errors(func
, *args
, **kwargs
):
908 """Catch exceptions raised by the main program function and then print
909 a message and/or exit with an appropriate return code.
912 traceback_str
= misc
.get_traceback_message()
915 # Out of memory errors can be raised as EnvironmentErrors with
916 # an errno of ENOMEM, so in order to handle those exceptions
917 # with other errnos, we nest this try block and have the outer
918 # one handle the other instances.
920 __ret
= func(*args
, **kwargs
)
921 except (MemoryError, EnvironmentError) as __e
:
922 if isinstance(__e
, EnvironmentError) and \
923 __e
.errno
!= errno
.ENOMEM
:
925 error("\n" + misc
.out_of_memory())
927 except SystemExit as __e
:
929 except (PipeError
, KeyboardInterrupt):
930 # Don't display any messages here to prevent possible further
931 # broken pipe (EPIPE) errors.
933 except apx
.VersionException
as __e
:
934 error(_("The sysrepo command appears out of sync with the "
935 "libraries provided\nby pkg:/package/pkg. The client "
936 "version is {client} while the library\nAPI version is "
937 "{api}.").format(client
=__e
.received_version
,
938 api
=__e
.expected_version
))
941 traceback
.print_exc()
947 if __name__
== "__main__":
948 misc
.setlocale(locale
.LC_ALL
, "", error
)
949 gettext
.install("pkg", "/usr/share/locale",
950 codeset
=locale
.getpreferredencoding())
952 # Make all warnings be errors.
953 warnings
.simplefilter('error')
955 __retval
= handle_errors(main_func
)
959 # Ignore python's spurious pipe problems.