Add initial files
Add initial files to the repo. Change-Id: Ia6b0278fc9817f44e63507b54e7d7ad41bf4e219
This commit is contained in:
parent
47c35bb357
commit
8136ec1b72
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Compiled files
|
||||||
|
*.py[co]
|
||||||
|
*.a
|
||||||
|
*.o
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Sphinx
|
||||||
|
_build
|
||||||
|
doc/source/api/
|
||||||
|
|
||||||
|
# Packages/installer info
|
||||||
|
*.egg
|
||||||
|
*.egg-info
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
eggs
|
||||||
|
parts
|
||||||
|
var
|
||||||
|
sdist
|
||||||
|
develop-eggs
|
||||||
|
.installed.cfg
|
||||||
|
|
||||||
|
# Other
|
||||||
|
*.DS_Store
|
||||||
|
.testrepository
|
||||||
|
.tox
|
||||||
|
.venv
|
||||||
|
.*.swp
|
||||||
|
.coverage
|
||||||
|
cover
|
||||||
|
AUTHORS
|
||||||
|
ChangeLog
|
||||||
|
*.sqlite
|
||||||
|
|
5
.testr.conf
Normal file
5
.testr.conf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -t ./ ./ $LISTOPT $IDOPTION
|
||||||
|
test_id_option=--load-list $IDFILE
|
||||||
|
test_list_option=--list
|
||||||
|
|
176
LICENSE
Normal file
176
LICENSE
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
6
MANIFEST.in
Normal file
6
MANIFEST.in
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
include AUTHORS
|
||||||
|
include ChangeLog
|
||||||
|
exclude .gitignore
|
||||||
|
exclude .gitreview
|
||||||
|
|
||||||
|
global-exclude *.pyc
|
5
README.rst
Normal file
5
README.rst
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Python bindings for the Ironic API
|
||||||
|
==================================
|
||||||
|
|
||||||
|
A python and command line client library for Ironic.
|
||||||
|
|
70
doc/source/conf.py
Normal file
70
doc/source/conf.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
# -- General configuration ----------------------------------------------------
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
|
extensions = ['sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.intersphinx',
|
||||||
|
'sphinx.ext.viewcode',
|
||||||
|
'oslo.sphinx'
|
||||||
|
]
|
||||||
|
|
||||||
|
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||||
|
# text edit cycles.
|
||||||
|
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix of source filenames.
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = u'python-ironicclient'
|
||||||
|
copyright = u'OpenStack Foundation'
|
||||||
|
|
||||||
|
# A list of ignored prefixes for module index sorting.
|
||||||
|
modindex_common_prefix = ['ironicclient.']
|
||||||
|
|
||||||
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
add_function_parentheses = True
|
||||||
|
|
||||||
|
# If true, the current module name will be prepended to all description
|
||||||
|
# unit titles (such as .. function::).
|
||||||
|
add_module_names = True
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# -- Options for HTML output --------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||||
|
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||||
|
html_theme_path = ["."]
|
||||||
|
html_theme = '_theme'
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = '%sdoc' % project
|
||||||
|
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title, author, documentclass
|
||||||
|
# [howto/manual]).
|
||||||
|
latex_documents = [
|
||||||
|
(
|
||||||
|
'index',
|
||||||
|
'%s.tex' % project,
|
||||||
|
u'%s Documentation' % project,
|
||||||
|
u'OpenStack LLC',
|
||||||
|
'manual'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
|
intersphinx_mapping = {'http://docs.python.org/': None}
|
14
ironicclient/__init__.py
Normal file
14
ironicclient/__init__.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
14
ironicclient/openstack/__init__.py
Normal file
14
ironicclient/openstack/__init__.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
14
ironicclient/openstack/common/__init__.py
Normal file
14
ironicclient/openstack/common/__init__.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
254
ironicclient/openstack/common/config/generator.py
Normal file
254
ironicclient/openstack/common/config/generator.py
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2012 SINA Corporation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
# @author: Zhongyue Luo, SINA Corporation.
|
||||||
|
#
|
||||||
|
"""Extracts OpenStack config option info from module(s)."""
|
||||||
|
|
||||||
|
import imp
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from ironicclient.openstack.common import gettextutils
|
||||||
|
from ironicclient.openstack.common import importutils
|
||||||
|
|
||||||
|
gettextutils.install('python-ironicclient')
|
||||||
|
|
||||||
|
STROPT = "StrOpt"
|
||||||
|
BOOLOPT = "BoolOpt"
|
||||||
|
INTOPT = "IntOpt"
|
||||||
|
FLOATOPT = "FloatOpt"
|
||||||
|
LISTOPT = "ListOpt"
|
||||||
|
MULTISTROPT = "MultiStrOpt"
|
||||||
|
|
||||||
|
OPT_TYPES = {
|
||||||
|
STROPT: 'string value',
|
||||||
|
BOOLOPT: 'boolean value',
|
||||||
|
INTOPT: 'integer value',
|
||||||
|
FLOATOPT: 'floating point value',
|
||||||
|
LISTOPT: 'list value',
|
||||||
|
MULTISTROPT: 'multi valued',
|
||||||
|
}
|
||||||
|
|
||||||
|
OPTION_COUNT = 0
|
||||||
|
OPTION_REGEX = re.compile(r"(%s)" % "|".join([STROPT, BOOLOPT, INTOPT,
|
||||||
|
FLOATOPT, LISTOPT,
|
||||||
|
MULTISTROPT]))
|
||||||
|
|
||||||
|
PY_EXT = ".py"
|
||||||
|
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||||
|
"../../../../"))
|
||||||
|
WORDWRAP_WIDTH = 60
|
||||||
|
|
||||||
|
|
||||||
|
def generate(srcfiles):
|
||||||
|
mods_by_pkg = dict()
|
||||||
|
for filepath in srcfiles:
|
||||||
|
pkg_name = filepath.split(os.sep)[1]
|
||||||
|
mod_str = '.'.join(['.'.join(filepath.split(os.sep)[:-1]),
|
||||||
|
os.path.basename(filepath).split('.')[0]])
|
||||||
|
mods_by_pkg.setdefault(pkg_name, list()).append(mod_str)
|
||||||
|
# NOTE(lzyeval): place top level modules before packages
|
||||||
|
pkg_names = filter(lambda x: x.endswith(PY_EXT), mods_by_pkg.keys())
|
||||||
|
pkg_names.sort()
|
||||||
|
ext_names = filter(lambda x: x not in pkg_names, mods_by_pkg.keys())
|
||||||
|
ext_names.sort()
|
||||||
|
pkg_names.extend(ext_names)
|
||||||
|
|
||||||
|
# opts_by_group is a mapping of group name to an options list
|
||||||
|
# The options list is a list of (module, options) tuples
|
||||||
|
opts_by_group = {'DEFAULT': []}
|
||||||
|
|
||||||
|
for pkg_name in pkg_names:
|
||||||
|
mods = mods_by_pkg.get(pkg_name)
|
||||||
|
mods.sort()
|
||||||
|
for mod_str in mods:
|
||||||
|
if mod_str.endswith('.__init__'):
|
||||||
|
mod_str = mod_str[:mod_str.rfind(".")]
|
||||||
|
|
||||||
|
mod_obj = _import_module(mod_str)
|
||||||
|
if not mod_obj:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for group, opts in _list_opts(mod_obj):
|
||||||
|
opts_by_group.setdefault(group, []).append((mod_str, opts))
|
||||||
|
|
||||||
|
print_group_opts('DEFAULT', opts_by_group.pop('DEFAULT', []))
|
||||||
|
for group, opts in opts_by_group.items():
|
||||||
|
print_group_opts(group, opts)
|
||||||
|
|
||||||
|
print "# Total option count: %d" % OPTION_COUNT
|
||||||
|
|
||||||
|
|
||||||
|
def _import_module(mod_str):
|
||||||
|
try:
|
||||||
|
if mod_str.startswith('bin.'):
|
||||||
|
imp.load_source(mod_str[4:], os.path.join('bin', mod_str[4:]))
|
||||||
|
return sys.modules[mod_str[4:]]
|
||||||
|
else:
|
||||||
|
return importutils.import_module(mod_str)
|
||||||
|
except ImportError as ie:
|
||||||
|
sys.stderr.write("%s\n" % str(ie))
|
||||||
|
return None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _is_in_group(opt, group):
|
||||||
|
"Check if opt is in group."
|
||||||
|
for key, value in group._opts.items():
|
||||||
|
if value['opt'] == opt:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _guess_groups(opt, mod_obj):
|
||||||
|
# is it in the DEFAULT group?
|
||||||
|
if _is_in_group(opt, cfg.CONF):
|
||||||
|
return 'DEFAULT'
|
||||||
|
|
||||||
|
# what other groups is it in?
|
||||||
|
for key, value in cfg.CONF.items():
|
||||||
|
if isinstance(value, cfg.CONF.GroupAttr):
|
||||||
|
if _is_in_group(opt, value._group):
|
||||||
|
return value._group.name
|
||||||
|
|
||||||
|
raise RuntimeError(
|
||||||
|
"Unable to find group for option %s, "
|
||||||
|
"maybe it's defined twice in the same group?"
|
||||||
|
% opt.name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _list_opts(obj):
|
||||||
|
def is_opt(o):
|
||||||
|
return (isinstance(o, cfg.Opt) and
|
||||||
|
not isinstance(o, cfg.SubCommandOpt))
|
||||||
|
|
||||||
|
opts = list()
|
||||||
|
for attr_str in dir(obj):
|
||||||
|
attr_obj = getattr(obj, attr_str)
|
||||||
|
if is_opt(attr_obj):
|
||||||
|
opts.append(attr_obj)
|
||||||
|
elif (isinstance(attr_obj, list) and
|
||||||
|
all(map(lambda x: is_opt(x), attr_obj))):
|
||||||
|
opts.extend(attr_obj)
|
||||||
|
|
||||||
|
ret = {}
|
||||||
|
for opt in opts:
|
||||||
|
ret.setdefault(_guess_groups(opt, obj), []).append(opt)
|
||||||
|
return ret.items()
|
||||||
|
|
||||||
|
|
||||||
|
def print_group_opts(group, opts_by_module):
|
||||||
|
print "[%s]" % group
|
||||||
|
print
|
||||||
|
global OPTION_COUNT
|
||||||
|
for mod, opts in opts_by_module:
|
||||||
|
OPTION_COUNT += len(opts)
|
||||||
|
print '#'
|
||||||
|
print '# Options defined in %s' % mod
|
||||||
|
print '#'
|
||||||
|
print
|
||||||
|
for opt in opts:
|
||||||
|
_print_opt(opt)
|
||||||
|
print
|
||||||
|
|
||||||
|
|
||||||
|
def _get_my_ip():
|
||||||
|
try:
|
||||||
|
csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
csock.connect(('8.8.8.8', 80))
|
||||||
|
(addr, port) = csock.getsockname()
|
||||||
|
csock.close()
|
||||||
|
return addr
|
||||||
|
except socket.error:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _sanitize_default(s):
|
||||||
|
"""Set up a reasonably sensible default for pybasedir, my_ip and host."""
|
||||||
|
if s.startswith(BASEDIR):
|
||||||
|
return s.replace(BASEDIR, '/usr/lib/python/site-packages')
|
||||||
|
elif BASEDIR in s:
|
||||||
|
return s.replace(BASEDIR, '')
|
||||||
|
elif s == _get_my_ip():
|
||||||
|
return '10.0.0.1'
|
||||||
|
elif s == socket.gethostname():
|
||||||
|
return 'python-ironicclient'
|
||||||
|
elif s.strip() != s:
|
||||||
|
return '"%s"' % s
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def _print_opt(opt):
|
||||||
|
opt_name, opt_default, opt_help = opt.dest, opt.default, opt.help
|
||||||
|
if not opt_help:
|
||||||
|
sys.stderr.write('WARNING: "%s" is missing help string.\n' % opt_name)
|
||||||
|
opt_type = None
|
||||||
|
try:
|
||||||
|
opt_type = OPTION_REGEX.search(str(type(opt))).group(0)
|
||||||
|
except (ValueError, AttributeError) as err:
|
||||||
|
sys.stderr.write("%s\n" % str(err))
|
||||||
|
sys.exit(1)
|
||||||
|
opt_help += ' (' + OPT_TYPES[opt_type] + ')'
|
||||||
|
print '#', "\n# ".join(textwrap.wrap(opt_help, WORDWRAP_WIDTH))
|
||||||
|
try:
|
||||||
|
if opt_default is None:
|
||||||
|
print '#%s=<None>' % opt_name
|
||||||
|
elif opt_type == STROPT:
|
||||||
|
assert(isinstance(opt_default, basestring))
|
||||||
|
print '#%s=%s' % (opt_name, _sanitize_default(opt_default))
|
||||||
|
elif opt_type == BOOLOPT:
|
||||||
|
assert(isinstance(opt_default, bool))
|
||||||
|
print '#%s=%s' % (opt_name, str(opt_default).lower())
|
||||||
|
elif opt_type == INTOPT:
|
||||||
|
assert(isinstance(opt_default, int) and
|
||||||
|
not isinstance(opt_default, bool))
|
||||||
|
print '#%s=%s' % (opt_name, opt_default)
|
||||||
|
elif opt_type == FLOATOPT:
|
||||||
|
assert(isinstance(opt_default, float))
|
||||||
|
print '#%s=%s' % (opt_name, opt_default)
|
||||||
|
elif opt_type == LISTOPT:
|
||||||
|
assert(isinstance(opt_default, list))
|
||||||
|
print '#%s=%s' % (opt_name, ','.join(opt_default))
|
||||||
|
elif opt_type == MULTISTROPT:
|
||||||
|
assert(isinstance(opt_default, list))
|
||||||
|
if not opt_default:
|
||||||
|
opt_default = ['']
|
||||||
|
for default in opt_default:
|
||||||
|
print '#%s=%s' % (opt_name, default)
|
||||||
|
print
|
||||||
|
except Exception:
|
||||||
|
sys.stderr.write('Error in option "%s"\n' % opt_name)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print "usage: %s [srcfile]...\n" % sys.argv[0]
|
||||||
|
sys.exit(0)
|
||||||
|
generate(sys.argv[1:])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
50
ironicclient/openstack/common/gettextutils.py
Normal file
50
ironicclient/openstack/common/gettextutils.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2012 Red Hat, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
gettext for openstack-common modules.
|
||||||
|
|
||||||
|
Usual usage in an openstack.common module:
|
||||||
|
|
||||||
|
from ironic.openstack.common.gettextutils import _
|
||||||
|
"""
|
||||||
|
|
||||||
|
import gettext
|
||||||
|
import os
|
||||||
|
|
||||||
|
_localedir = os.environ.get('ironicclient'.upper() + '_LOCALEDIR')
|
||||||
|
_t = gettext.translation('ironicclient', localedir=_localedir, fallback=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _(msg):
|
||||||
|
return _t.ugettext(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def install(domain):
|
||||||
|
"""Install a _() function using the given translation domain.
|
||||||
|
|
||||||
|
Given a translation domain, install a _() function using gettext's
|
||||||
|
install() function.
|
||||||
|
|
||||||
|
The main difference from gettext.install() is that we allow
|
||||||
|
overriding the default localedir (e.g. /usr/share/locale) using
|
||||||
|
a translation-domain-specific environment variable (e.g.
|
||||||
|
NOVA_LOCALEDIR).
|
||||||
|
"""
|
||||||
|
gettext.install(domain,
|
||||||
|
localedir=os.environ.get(domain.upper() + '_LOCALEDIR'),
|
||||||
|
unicode=True)
|
67
ironicclient/openstack/common/importutils.py
Normal file
67
ironicclient/openstack/common/importutils.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 OpenStack Foundation.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Import related utilities and helper functions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
def import_class(import_str):
|
||||||
|
"""Returns a class from a string including module and class"""
|
||||||
|
mod_str, _sep, class_str = import_str.rpartition('.')
|
||||||
|
try:
|
||||||
|
__import__(mod_str)
|
||||||
|
return getattr(sys.modules[mod_str], class_str)
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
raise ImportError('Class %s cannot be found (%s)' %
|
||||||
|
(class_str,
|
||||||
|
traceback.format_exception(*sys.exc_info())))
|
||||||
|
|
||||||
|
|
||||||
|
def import_object(import_str, *args, **kwargs):
|
||||||
|
"""Import a class and return an instance of it."""
|
||||||
|
return import_class(import_str)(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def import_object_ns(name_space, import_str, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Import a class and return an instance of it, first by trying
|
||||||
|
to find the class in a default namespace, then failing back to
|
||||||
|
a full path if not found in the default namespace.
|
||||||
|
"""
|
||||||
|
import_value = "%s.%s" % (name_space, import_str)
|
||||||
|
try:
|
||||||
|
return import_class(import_value)(*args, **kwargs)
|
||||||
|
except ImportError:
|
||||||
|
return import_class(import_str)(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def import_module(import_str):
|
||||||
|
"""Import a module."""
|
||||||
|
__import__(import_str)
|
||||||
|
return sys.modules[import_str]
|
||||||
|
|
||||||
|
|
||||||
|
def try_import(import_str, default=None):
|
||||||
|
"""Try to import a module and if it fails return default."""
|
||||||
|
try:
|
||||||
|
return import_module(import_str)
|
||||||
|
except ImportError:
|
||||||
|
return default
|
16
ironicclient/openstack/common/rootwrap/__init__.py
Normal file
16
ironicclient/openstack/common/rootwrap/__init__.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (c) 2011 OpenStack Foundation.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
119
ironicclient/openstack/common/rootwrap/cmd.py
Executable file
119
ironicclient/openstack/common/rootwrap/cmd.py
Executable file
@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (c) 2011 OpenStack Foundation.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Root wrapper for OpenStack services
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import ConfigParser
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import pwd
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
RC_UNAUTHORIZED = 99
|
||||||
|
RC_NOCOMMAND = 98
|
||||||
|
RC_BADCONFIG = 97
|
||||||
|
RC_NOEXECFOUND = 96
|
||||||
|
|
||||||
|
|
||||||
|
def _subprocess_setup():
|
||||||
|
# Python installs a SIGPIPE handler by default. This is usually not what
|
||||||
|
# non-Python subprocesses expect.
|
||||||
|
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||||
|
|
||||||
|
|
||||||
|
def _exit_error(execname, message, errorcode, log=True):
|
||||||
|
print("%s: %s" % (execname, message))
|
||||||
|
if log:
|
||||||
|
logging.error(message)
|
||||||
|
sys.exit(errorcode)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Split arguments, require at least a command
|
||||||
|
execname = sys.argv.pop(0)
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
_exit_error(execname, "No command specified", RC_NOCOMMAND, log=False)
|
||||||
|
|
||||||
|
configfile = sys.argv.pop(0)
|
||||||
|
userargs = sys.argv[:]
|
||||||
|
|
||||||
|
# Add ../ to sys.path to allow running from branch
|
||||||
|
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(execname),
|
||||||
|
os.pardir, os.pardir))
|
||||||
|
if os.path.exists(os.path.join(possible_topdir, "ironicclient",
|
||||||
|
"__init__.py")):
|
||||||
|
sys.path.insert(0, possible_topdir)
|
||||||
|
|
||||||
|
from ironicclient.openstack.common.rootwrap import wrapper
|
||||||
|
|
||||||
|
# Load configuration
|
||||||
|
try:
|
||||||
|
rawconfig = ConfigParser.RawConfigParser()
|
||||||
|
rawconfig.read(configfile)
|
||||||
|
config = wrapper.RootwrapConfig(rawconfig)
|
||||||
|
except ValueError as exc:
|
||||||
|
msg = "Incorrect value in %s: %s" % (configfile, exc.message)
|
||||||
|
_exit_error(execname, msg, RC_BADCONFIG, log=False)
|
||||||
|
except ConfigParser.Error:
|
||||||
|
_exit_error(execname, "Incorrect configuration file: %s" % configfile,
|
||||||
|
RC_BADCONFIG, log=False)
|
||||||
|
|
||||||
|
if config.use_syslog:
|
||||||
|
wrapper.setup_syslog(execname,
|
||||||
|
config.syslog_log_facility,
|
||||||
|
config.syslog_log_level)
|
||||||
|
|
||||||
|
# Execute command if it matches any of the loaded filters
|
||||||
|
filters = wrapper.load_filters(config.filters_path)
|
||||||
|
try:
|
||||||
|
filtermatch = wrapper.match_filter(filters, userargs,
|
||||||
|
exec_dirs=config.exec_dirs)
|
||||||
|
if filtermatch:
|
||||||
|
command = filtermatch.get_command(userargs,
|
||||||
|
exec_dirs=config.exec_dirs)
|
||||||
|
if config.use_syslog:
|
||||||
|
logging.info("(%s > %s) Executing %s (filter match = %s)" % (
|
||||||
|
os.getlogin(), pwd.getpwuid(os.getuid())[0],
|
||||||
|
command, filtermatch.name))
|
||||||
|
|
||||||
|
obj = subprocess.Popen(command,
|
||||||
|
stdin=sys.stdin,
|
||||||
|
stdout=sys.stdout,
|
||||||
|
stderr=sys.stderr,
|
||||||
|
preexec_fn=_subprocess_setup,
|
||||||
|
env=filtermatch.get_environment(userargs))
|
||||||
|
obj.wait()
|
||||||
|
sys.exit(obj.returncode)
|
||||||
|
|
||||||
|
except wrapper.FilterMatchNotExecutable as exc:
|
||||||
|
msg = ("Executable not found: %s (filter match = %s)"
|
||||||
|
% (exc.match.exec_path, exc.match.name))
|
||||||
|
_exit_error(execname, msg, RC_NOEXECFOUND, log=config.use_syslog)
|
||||||
|
|
||||||
|
except wrapper.NoFilterMatched:
|
||||||
|
msg = ("Unauthorized command: %s (no filter matched)"
|
||||||
|
% ' '.join(userargs))
|
||||||
|
_exit_error(execname, msg, RC_UNAUTHORIZED, log=config.use_syslog)
|
228
ironicclient/openstack/common/rootwrap/filters.py
Normal file
228
ironicclient/openstack/common/rootwrap/filters.py
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (c) 2011 OpenStack Foundation.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class CommandFilter(object):
|
||||||
|
"""Command filter only checking that the 1st argument matches exec_path."""
|
||||||
|
|
||||||
|
def __init__(self, exec_path, run_as, *args):
|
||||||
|
self.name = ''
|
||||||
|
self.exec_path = exec_path
|
||||||
|
self.run_as = run_as
|
||||||
|
self.args = args
|
||||||
|
self.real_exec = None
|
||||||
|
|
||||||
|
def get_exec(self, exec_dirs=[]):
|
||||||
|
"""Returns existing executable, or empty string if none found."""
|
||||||
|
if self.real_exec is not None:
|
||||||
|
return self.real_exec
|
||||||
|
self.real_exec = ""
|
||||||
|
if self.exec_path.startswith('/'):
|
||||||
|
if os.access(self.exec_path, os.X_OK):
|
||||||
|
self.real_exec = self.exec_path
|
||||||
|
else:
|
||||||
|
for binary_path in exec_dirs:
|
||||||
|
expanded_path = os.path.join(binary_path, self.exec_path)
|
||||||
|
if os.access(expanded_path, os.X_OK):
|
||||||
|
self.real_exec = expanded_path
|
||||||
|
break
|
||||||
|
return self.real_exec
|
||||||
|
|
||||||
|
def match(self, userargs):
|
||||||
|
"""Only check that the first argument (command) matches exec_path."""
|
||||||
|
return os.path.basename(self.exec_path) == userargs[0]
|
||||||
|
|
||||||
|
def get_command(self, userargs, exec_dirs=[]):
|
||||||
|
"""Returns command to execute (with sudo -u if run_as != root)."""
|
||||||
|
to_exec = self.get_exec(exec_dirs=exec_dirs) or self.exec_path
|
||||||
|
if (self.run_as != 'root'):
|
||||||
|
# Used to run commands at lesser privileges
|
||||||
|
return ['sudo', '-u', self.run_as, to_exec] + userargs[1:]
|
||||||
|
return [to_exec] + userargs[1:]
|
||||||
|
|
||||||
|
def get_environment(self, userargs):
|
||||||
|
"""Returns specific environment to set, None if none."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class RegExpFilter(CommandFilter):
|
||||||
|
"""Command filter doing regexp matching for every argument."""
|
||||||
|
|
||||||
|
def match(self, userargs):
|
||||||
|
# Early skip if command or number of args don't match
|
||||||
|
if (len(self.args) != len(userargs)):
|
||||||
|
# DENY: argument numbers don't match
|
||||||
|
return False
|
||||||
|
# Compare each arg (anchoring pattern explicitly at end of string)
|
||||||
|
for (pattern, arg) in zip(self.args, userargs):
|
||||||
|
try:
|
||||||
|
if not re.match(pattern + '$', arg):
|
||||||
|
break
|
||||||
|
except re.error:
|
||||||
|
# DENY: Badly-formed filter
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# ALLOW: All arguments matched
|
||||||
|
return True
|
||||||
|
|
||||||
|
# DENY: Some arguments did not match
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class PathFilter(CommandFilter):
|
||||||
|
"""Command filter checking that path arguments are within given dirs
|
||||||
|
|
||||||
|
One can specify the following constraints for command arguments:
|
||||||
|
1) pass - pass an argument as is to the resulting command
|
||||||
|
2) some_str - check if an argument is equal to the given string
|
||||||
|
3) abs path - check if a path argument is within the given base dir
|
||||||
|
|
||||||
|
A typical rootwrapper filter entry looks like this:
|
||||||
|
# cmdname: filter name, raw command, user, arg_i_constraint [, ...]
|
||||||
|
chown: PathFilter, /bin/chown, root, nova, /var/lib/images
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def match(self, userargs):
|
||||||
|
command, arguments = userargs[0], userargs[1:]
|
||||||
|
|
||||||
|
equal_args_num = len(self.args) == len(arguments)
|
||||||
|
exec_is_valid = super(PathFilter, self).match(userargs)
|
||||||
|
args_equal_or_pass = all(
|
||||||
|
arg == 'pass' or arg == value
|
||||||
|
for arg, value in zip(self.args, arguments)
|
||||||
|
if not os.path.isabs(arg) # arguments not specifying abs paths
|
||||||
|
)
|
||||||
|
paths_are_within_base_dirs = all(
|
||||||
|
os.path.commonprefix([arg, os.path.realpath(value)]) == arg
|
||||||
|
for arg, value in zip(self.args, arguments)
|
||||||
|
if os.path.isabs(arg) # arguments specifying abs paths
|
||||||
|
)
|
||||||
|
|
||||||
|
return (equal_args_num and
|
||||||
|
exec_is_valid and
|
||||||
|
args_equal_or_pass and
|
||||||
|
paths_are_within_base_dirs)
|
||||||
|
|
||||||
|
def get_command(self, userargs, exec_dirs=[]):
|
||||||
|
command, arguments = userargs[0], userargs[1:]
|
||||||
|
|
||||||
|
# convert path values to canonical ones; copy other args as is
|
||||||
|
args = [os.path.realpath(value) if os.path.isabs(arg) else value
|
||||||
|
for arg, value in zip(self.args, arguments)]
|
||||||
|
|
||||||
|
return super(PathFilter, self).get_command([command] + args,
|
||||||
|
exec_dirs)
|
||||||
|
|
||||||
|
|
||||||
|
class DnsmasqFilter(CommandFilter):
|
||||||
|
"""Specific filter for the dnsmasq call (which includes env)."""
|
||||||
|
|
||||||
|
CONFIG_FILE_ARG = 'CONFIG_FILE'
|
||||||
|
|
||||||
|
def match(self, userargs):
|
||||||
|
if (userargs[0] == 'env' and
|
||||||
|
userargs[1].startswith(self.CONFIG_FILE_ARG) and
|
||||||
|
userargs[2].startswith('NETWORK_ID=') and
|
||||||
|
userargs[3] == 'dnsmasq'):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_command(self, userargs, exec_dirs=[]):
|
||||||
|
to_exec = self.get_exec(exec_dirs=exec_dirs) or self.exec_path
|
||||||
|
dnsmasq_pos = userargs.index('dnsmasq')
|
||||||
|
return [to_exec] + userargs[dnsmasq_pos + 1:]
|
||||||
|
|
||||||
|
def get_environment(self, userargs):
|
||||||
|
env = os.environ.copy()
|
||||||
|
env[self.CONFIG_FILE_ARG] = userargs[1].split('=')[-1]
|
||||||
|
env['NETWORK_ID'] = userargs[2].split('=')[-1]
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
class DeprecatedDnsmasqFilter(DnsmasqFilter):
|
||||||
|
"""Variant of dnsmasq filter to support old-style FLAGFILE."""
|
||||||
|
CONFIG_FILE_ARG = 'FLAGFILE'
|
||||||
|
|
||||||
|
|
||||||
|
class KillFilter(CommandFilter):
|
||||||
|
"""Specific filter for the kill calls.
|
||||||
|
1st argument is the user to run /bin/kill under
|
||||||
|
2nd argument is the location of the affected executable
|
||||||
|
Subsequent arguments list the accepted signals (if any)
|
||||||
|
|
||||||
|
This filter relies on /proc to accurately determine affected
|
||||||
|
executable, so it will only work on procfs-capable systems (not OSX).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super(KillFilter, self).__init__("/bin/kill", *args)
|
||||||
|
|
||||||
|
def match(self, userargs):
|
||||||
|
if userargs[0] != "kill":
|
||||||
|
return False
|
||||||
|
args = list(userargs)
|
||||||
|
if len(args) == 3:
|
||||||
|
# A specific signal is requested
|
||||||
|
signal = args.pop(1)
|
||||||
|
if signal not in self.args[1:]:
|
||||||
|
# Requested signal not in accepted list
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if len(args) != 2:
|
||||||
|
# Incorrect number of arguments
|
||||||
|
return False
|
||||||
|
if len(self.args) > 1:
|
||||||
|
# No signal requested, but filter requires specific signal
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
command = os.readlink("/proc/%d/exe" % int(args[1]))
|
||||||
|
# NOTE(yufang521247): /proc/PID/exe may have '\0' on the
|
||||||
|
# end, because python doen't stop at '\0' when read the
|
||||||
|
# target path.
|
||||||
|
command = command.split('\0')[0]
|
||||||
|
# NOTE(dprince): /proc/PID/exe may have ' (deleted)' on
|
||||||
|
# the end if an executable is updated or deleted
|
||||||
|
if command.endswith(" (deleted)"):
|
||||||
|
command = command[:command.rindex(" ")]
|
||||||
|
if command != self.args[0]:
|
||||||
|
# Affected executable does not match
|
||||||
|
return False
|
||||||
|
except (ValueError, OSError):
|
||||||
|
# Incorrect PID
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class ReadFileFilter(CommandFilter):
|
||||||
|
"""Specific filter for the utils.read_file_as_root call."""
|
||||||
|
|
||||||
|
def __init__(self, file_path, *args):
|
||||||
|
self.file_path = file_path
|
||||||
|
super(ReadFileFilter, self).__init__("/bin/cat", "root", *args)
|
||||||
|
|
||||||
|
def match(self, userargs):
|
||||||
|
if userargs[0] != 'cat':
|
||||||
|
return False
|
||||||
|
if userargs[1] != self.file_path:
|
||||||
|
return False
|
||||||
|
if len(userargs) != 2:
|
||||||
|
return False
|
||||||
|
return True
|
151
ironicclient/openstack/common/rootwrap/wrapper.py
Normal file
151
ironicclient/openstack/common/rootwrap/wrapper.py
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (c) 2011 OpenStack Foundation.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import ConfigParser
|
||||||
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
import os
|
||||||
|
import string
|
||||||
|
|
||||||
|
from ironicclient.openstack.common.rootwrap import filters
|
||||||
|
|
||||||
|
|
||||||
|
class NoFilterMatched(Exception):
|
||||||
|
"""This exception is raised when no filter matched."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FilterMatchNotExecutable(Exception):
|
||||||
|
"""raise if filter matche but not executable
|
||||||
|
|
||||||
|
This exception is raised when a filter matched but no executable was
|
||||||
|
found.
|
||||||
|
"""
|
||||||
|
def __init__(self, match=None, **kwargs):
|
||||||
|
self.match = match
|
||||||
|
|
||||||
|
|
||||||
|
class RootwrapConfig(object):
|
||||||
|
|
||||||
|
def __init__(self, config):
|
||||||
|
# filters_path
|
||||||
|
self.filters_path = config.get("DEFAULT", "filters_path").split(",")
|
||||||
|
|
||||||
|
# exec_dirs
|
||||||
|
if config.has_option("DEFAULT", "exec_dirs"):
|
||||||
|
self.exec_dirs = config.get("DEFAULT", "exec_dirs").split(",")
|
||||||
|
else:
|
||||||
|
# Use system PATH if exec_dirs is not specified
|
||||||
|
self.exec_dirs = os.environ["PATH"].split(':')
|
||||||
|
|
||||||
|
# syslog_log_facility
|
||||||
|
if config.has_option("DEFAULT", "syslog_log_facility"):
|
||||||
|
v = config.get("DEFAULT", "syslog_log_facility")
|
||||||
|
facility_names = logging.handlers.SysLogHandler.facility_names
|
||||||
|
self.syslog_log_facility = getattr(logging.handlers.SysLogHandler,
|
||||||
|
v, None)
|
||||||
|
if self.syslog_log_facility is None and v in facility_names:
|
||||||
|
self.syslog_log_facility = facility_names.get(v)
|
||||||
|
if self.syslog_log_facility is None:
|
||||||
|
raise ValueError('Unexpected syslog_log_facility: %s' % v)
|
||||||
|
else:
|
||||||
|
default_facility = logging.handlers.SysLogHandler.LOG_SYSLOG
|
||||||
|
self.syslog_log_facility = default_facility
|
||||||
|
|
||||||
|
# syslog_log_level
|
||||||
|
if config.has_option("DEFAULT", "syslog_log_level"):
|
||||||
|
v = config.get("DEFAULT", "syslog_log_level")
|
||||||
|
self.syslog_log_level = logging.getLevelName(v.upper())
|
||||||
|
if (self.syslog_log_level == "Level %s" % v.upper()):
|
||||||
|
raise ValueError('Unexepected syslog_log_level: %s' % v)
|
||||||
|
else:
|
||||||
|
self.syslog_log_level = logging.ERROR
|
||||||
|
|
||||||
|
# use_syslog
|
||||||
|
if config.has_option("DEFAULT", "use_syslog"):
|
||||||
|
self.use_syslog = config.getboolean("DEFAULT", "use_syslog")
|
||||||
|
else:
|
||||||
|
self.use_syslog = False
|
||||||
|
|
||||||
|
|
||||||
|
def setup_syslog(execname, facility, level):
|
||||||
|
rootwrap_logger = logging.getLogger()
|
||||||
|
rootwrap_logger.setLevel(level)
|
||||||
|
handler = logging.handlers.SysLogHandler(address='/dev/log',
|
||||||
|
facility=facility)
|
||||||
|
handler.setFormatter(logging.Formatter(
|
||||||
|
os.path.basename(execname) + ': %(message)s'))
|
||||||
|
rootwrap_logger.addHandler(handler)
|
||||||
|
|
||||||
|
|
||||||
|
def build_filter(class_name, *args):
|
||||||
|
"""Returns a filter object of class class_name."""
|
||||||
|
if not hasattr(filters, class_name):
|
||||||
|
logging.warning("Skipping unknown filter class (%s) specified "
|
||||||
|
"in filter definitions" % class_name)
|
||||||
|
return None
|
||||||
|
filterclass = getattr(filters, class_name)
|
||||||
|
return filterclass(*args)
|
||||||
|
|
||||||
|
|
||||||
|
def load_filters(filters_path):
|
||||||
|
"""Load filters from a list of directories."""
|
||||||
|
filterlist = []
|
||||||
|
for filterdir in filters_path:
|
||||||
|
if not os.path.isdir(filterdir):
|
||||||
|
continue
|
||||||
|
for filterfile in os.listdir(filterdir):
|
||||||
|
filterconfig = ConfigParser.RawConfigParser()
|
||||||
|
filterconfig.read(os.path.join(filterdir, filterfile))
|
||||||
|
for (name, value) in filterconfig.items("Filters"):
|
||||||
|
filterdefinition = [string.strip(s) for s in value.split(',')]
|
||||||
|
newfilter = build_filter(*filterdefinition)
|
||||||
|
if newfilter is None:
|
||||||
|
continue
|
||||||
|
newfilter.name = name
|
||||||
|
filterlist.append(newfilter)
|
||||||
|
return filterlist
|
||||||
|
|
||||||
|
|
||||||
|
def match_filter(filter_list, userargs, exec_dirs=[]):
|
||||||
|
"""check user command and args
|
||||||
|
|
||||||
|
Checks user command and arguments through command filters and
|
||||||
|
returns the first matching filter.
|
||||||
|
Raises NoFilterMatched if no filter matched.
|
||||||
|
Raises FilterMatchNotExecutable if no executable was found for the
|
||||||
|
best filter match.
|
||||||
|
"""
|
||||||
|
first_not_executable_filter = None
|
||||||
|
|
||||||
|
for f in filter_list:
|
||||||
|
if f.match(userargs):
|
||||||
|
# Try other filters if executable is absent
|
||||||
|
if not f.get_exec(exec_dirs=exec_dirs):
|
||||||
|
if not first_not_executable_filter:
|
||||||
|
first_not_executable_filter = f
|
||||||
|
continue
|
||||||
|
# Otherwise return matching filter for execution
|
||||||
|
return f
|
||||||
|
|
||||||
|
if first_not_executable_filter:
|
||||||
|
# A filter matched, but no executable was found for it
|
||||||
|
raise FilterMatchNotExecutable(match=first_not_executable_filter)
|
||||||
|
|
||||||
|
# No filter matched
|
||||||
|
raise NoFilterMatched()
|
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
pbr>=0.5.21,<1.0
|
||||||
|
anyjson>=0.3.3
|
||||||
|
argparse
|
||||||
|
httplib2
|
||||||
|
lxml>=2.3
|
||||||
|
PrettyTable>=0.6,<0.8
|
34
setup.cfg
Normal file
34
setup.cfg
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
[metadata]
|
||||||
|
name = python-ironicclient
|
||||||
|
summary = OpenStack Bare Metal Provisioning API Client Library
|
||||||
|
description-file = README.rst
|
||||||
|
author = OpenStack
|
||||||
|
author-email = openstack-dev@lists.openstack.org
|
||||||
|
home-page = http://www.openstack.org/
|
||||||
|
classifier =
|
||||||
|
Environment :: OpenStack
|
||||||
|
Intended Audience :: Information Technology
|
||||||
|
Intended Audience :: System Administrators
|
||||||
|
License :: OSI Approved :: Apache Software License
|
||||||
|
Operating System :: POSIX :: Linux
|
||||||
|
Programming Language :: Python
|
||||||
|
Programming Language :: Python :: 2
|
||||||
|
Programming Language :: Python :: 2.7
|
||||||
|
Programming Language :: Python :: 2.6
|
||||||
|
Programming Language :: Python :: 3
|
||||||
|
Programming Language :: Python :: 3.3
|
||||||
|
|
||||||
|
[files]
|
||||||
|
packages = ironicclient
|
||||||
|
|
||||||
|
[entry_points]
|
||||||
|
console_scripts =
|
||||||
|
ironicclient = ironicclient.openstack.common.rootwrap.cmd:main
|
||||||
|
|
||||||
|
[pbr]
|
||||||
|
autodoc_index_modules = True
|
||||||
|
|
||||||
|
[build_sphinx]
|
||||||
|
all_files = 1
|
||||||
|
build-dir = doc/build
|
||||||
|
source-dir = doc/source
|
22
setup.py
Normal file
22
setup.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||||
|
import setuptools
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
setup_requires=['pbr>=0.5.21,<1.0'],
|
||||||
|
pbr=True)
|
10
test-requirements.txt
Normal file
10
test-requirements.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
hacking>=0.5.6,<0.8
|
||||||
|
coverage>=3.6
|
||||||
|
discover
|
||||||
|
fixtures>=0.3.14
|
||||||
|
mock>=1.0
|
||||||
|
Babel>=0.9.6
|
||||||
|
python-subunit
|
||||||
|
sphinx>=1.1.2
|
||||||
|
testrepository>=0.0.17
|
||||||
|
testtools>=0.9.32
|
0
tools/__init__.py
Normal file
0
tools/__init__.py
Normal file
212
tools/install_venv_common.py
Normal file
212
tools/install_venv_common.py
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2013 OpenStack Foundation
|
||||||
|
# Copyright 2013 IBM Corp.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""Provides methods needed by installation script for OpenStack development
|
||||||
|
virtual environments.
|
||||||
|
|
||||||
|
Since this script is used to bootstrap a virtualenv from the system's Python
|
||||||
|
environment, it should be kept strictly compatible with Python 2.6.
|
||||||
|
|
||||||
|
Synced in from openstack-common
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import optparse
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
class InstallVenv(object):
|
||||||
|
|
||||||
|
def __init__(self, root, venv, requirements,
|
||||||
|
test_requirements, py_version,
|
||||||
|
project):
|
||||||
|
self.root = root
|
||||||
|
self.venv = venv
|
||||||
|
self.requirements = requirements
|
||||||
|
self.test_requirements = test_requirements
|
||||||
|
self.py_version = py_version
|
||||||
|
self.project = project
|
||||||
|
|
||||||
|
def die(self, message, *args):
|
||||||
|
print(message % args, file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def check_python_version(self):
|
||||||
|
if sys.version_info < (2, 6):
|
||||||
|
self.die("Need Python Version >= 2.6")
|
||||||
|
|
||||||
|
def run_command_with_code(self, cmd, redirect_output=True,
|
||||||
|
check_exit_code=True):
|
||||||
|
"""Runs a command in an out-of-process shell.
|
||||||
|
|
||||||
|
Returns the output of that command. Working directory is self.root.
|
||||||
|
"""
|
||||||
|
if redirect_output:
|
||||||
|
stdout = subprocess.PIPE
|
||||||
|
else:
|
||||||
|
stdout = None
|
||||||
|
|
||||||
|
proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout)
|
||||||
|
output = proc.communicate()[0]
|
||||||
|
if check_exit_code and proc.returncode != 0:
|
||||||
|
self.die('Command "%s" failed.\n%s', ' '.join(cmd), output)
|
||||||
|
return (output, proc.returncode)
|
||||||
|
|
||||||
|
def run_command(self, cmd, redirect_output=True, check_exit_code=True):
|
||||||
|
return self.run_command_with_code(cmd, redirect_output,
|
||||||
|
check_exit_code)[0]
|
||||||
|
|
||||||
|
def get_distro(self):
|
||||||
|
if (os.path.exists('/etc/fedora-release') or
|
||||||
|
os.path.exists('/etc/redhat-release')):
|
||||||
|
return Fedora(
|
||||||
|
self.root, self.venv, self.requirements,
|
||||||
|
self.test_requirements, self.py_version, self.project)
|
||||||
|
else:
|
||||||
|
return Distro(
|
||||||
|
self.root, self.venv, self.requirements,
|
||||||
|
self.test_requirements, self.py_version, self.project)
|
||||||
|
|
||||||
|
def check_dependencies(self):
|
||||||
|
self.get_distro().install_virtualenv()
|
||||||
|
|
||||||
|
def create_virtualenv(self, no_site_packages=True):
|
||||||
|
"""Creates the virtual environment and installs PIP.
|
||||||
|
|
||||||
|
Creates the virtual environment and installs PIP only into the
|
||||||
|
virtual environment.
|
||||||
|
"""
|
||||||
|
if not os.path.isdir(self.venv):
|
||||||
|
print('Creating venv...', end=' ')
|
||||||
|
if no_site_packages:
|
||||||
|
self.run_command(['virtualenv', '-q', '--no-site-packages',
|
||||||
|
self.venv])
|
||||||
|
else:
|
||||||
|
self.run_command(['virtualenv', '-q', self.venv])
|
||||||
|
print('done.')
|
||||||
|
else:
|
||||||
|
print("venv already exists...")
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pip_install(self, *args):
|
||||||
|
self.run_command(['tools/with_venv.sh',
|
||||||
|
'pip', 'install', '--upgrade'] + list(args),
|
||||||
|
redirect_output=False)
|
||||||
|
|
||||||
|
def install_dependencies(self):
|
||||||
|
print('Installing dependencies with pip (this can take a while)...')
|
||||||
|
|
||||||
|
# First things first, make sure our venv has the latest pip and
|
||||||
|
# setuptools.
|
||||||
|
self.pip_install('pip>=1.3')
|
||||||
|
self.pip_install('setuptools')
|
||||||
|
|
||||||
|
self.pip_install('-r', self.requirements)
|
||||||
|
self.pip_install('-r', self.test_requirements)
|
||||||
|
|
||||||
|
def post_process(self):
|
||||||
|
self.get_distro().post_process()
|
||||||
|
|
||||||
|
def parse_args(self, argv):
|
||||||
|
"""Parses command-line arguments."""
|
||||||
|
parser = optparse.OptionParser()
|
||||||
|
parser.add_option('-n', '--no-site-packages',
|
||||||
|
action='store_true',
|
||||||
|
help="Do not inherit packages from global Python "
|
||||||
|
"install")
|
||||||
|
return parser.parse_args(argv[1:])[0]
|
||||||
|
|
||||||
|
|
||||||
|
class Distro(InstallVenv):
|
||||||
|
|
||||||
|
def check_cmd(self, cmd):
|
||||||
|
return bool(self.run_command(['which', cmd],
|
||||||
|
check_exit_code=False).strip())
|
||||||
|
|
||||||
|
def install_virtualenv(self):
|
||||||
|
if self.check_cmd('virtualenv'):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.check_cmd('easy_install'):
|
||||||
|
print('Installing virtualenv via easy_install...', end=' ')
|
||||||
|
if self.run_command(['easy_install', 'virtualenv']):
|
||||||
|
print('Succeeded')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print('Failed')
|
||||||
|
|
||||||
|
self.die('ERROR: virtualenv not found.\n\n%s development'
|
||||||
|
' requires virtualenv, please install it using your'
|
||||||
|
' favorite package management tool' % self.project)
|
||||||
|
|
||||||
|
def post_process(self):
|
||||||
|
"""Any distribution-specific post-processing gets done here.
|
||||||
|
|
||||||
|
In particular, this is useful for applying patches to code inside
|
||||||
|
the venv.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Fedora(Distro):
|
||||||
|
"""This covers all Fedora-based distributions.
|
||||||
|
|
||||||
|
Includes: Fedora, RHEL, CentOS, Scientific Linux
|
||||||
|
"""
|
||||||
|
|
||||||
|
def check_pkg(self, pkg):
|
||||||
|
return self.run_command_with_code(['rpm', '-q', pkg],
|
||||||
|
check_exit_code=False)[1] == 0
|
||||||
|
|
||||||
|
def apply_patch(self, originalfile, patchfile):
|
||||||
|
self.run_command(['patch', '-N', originalfile, patchfile],
|
||||||
|
check_exit_code=False)
|
||||||
|
|
||||||
|
def install_virtualenv(self):
|
||||||
|
if self.check_cmd('virtualenv'):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.check_pkg('python-virtualenv'):
|
||||||
|
self.die("Please install 'python-virtualenv'.")
|
||||||
|
|
||||||
|
super(Fedora, self).install_virtualenv()
|
||||||
|
|
||||||
|
def post_process(self):
|
||||||
|
"""Workaround for a bug in eventlet.
|
||||||
|
|
||||||
|
This currently affects RHEL6.1, but the fix can safely be
|
||||||
|
applied to all RHEL and Fedora distributions.
|
||||||
|
|
||||||
|
This can be removed when the fix is applied upstream.
|
||||||
|
|
||||||
|
Nova: https://bugs.launchpad.net/nova/+bug/884915
|
||||||
|
Upstream: https://bitbucket.org/eventlet/eventlet/issue/89
|
||||||
|
RHEL: https://bugzilla.redhat.com/958868
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Install "patch" program if it's not there
|
||||||
|
if not self.check_pkg('patch'):
|
||||||
|
self.die("Please install 'patch'.")
|
||||||
|
|
||||||
|
# Apply the eventlet patch
|
||||||
|
self.apply_patch(os.path.join(self.venv, 'lib', self.py_version,
|
||||||
|
'site-packages',
|
||||||
|
'eventlet/green/subprocess.py'),
|
||||||
|
'contrib/redhat-eventlet.patch')
|
7
tools/with_venv.sh
Executable file
7
tools/with_venv.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
tools_path=${tools_path:-$(dirname $0)}
|
||||||
|
venv_path=${venv_path:-${tools_path}}
|
||||||
|
venv_dir=${venv_name:-/../.venv}
|
||||||
|
TOOLS=${tools_path}
|
||||||
|
VENV=${venv:-${venv_path}/${venv_dir}}
|
||||||
|
source ${VENV}/bin/activate && "$@"
|
36
tox.ini
Normal file
36
tox.ini
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
[tox]
|
||||||
|
minversion = 1.6
|
||||||
|
envlist = py26,py27,py33,pep8
|
||||||
|
skipsdist = True
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
setenv = VIRTUAL_ENV={envdir}
|
||||||
|
LANG=en_US.UTF-8
|
||||||
|
LANGUAGE=en_US:en
|
||||||
|
LC_ALL=C
|
||||||
|
deps =
|
||||||
|
-r{toxinidir}/requirements.txt
|
||||||
|
-r{toxinidir}/test-requirements.txt
|
||||||
|
commands =
|
||||||
|
python setup.py testr --slowest --testr-args='{posargs}'
|
||||||
|
|
||||||
|
[tox:jenkins]
|
||||||
|
downloadcache = ~/cache/pip
|
||||||
|
|
||||||
|
[testenv:pep8]
|
||||||
|
commands =
|
||||||
|
flake8 {posargs}
|
||||||
|
|
||||||
|
[testenv:cover]
|
||||||
|
setenv = VIRTUAL_ENV={envdir}
|
||||||
|
commands =
|
||||||
|
python tools/patch_tox_venv.py
|
||||||
|
python setup.py testr --coverage {posargs}
|
||||||
|
|
||||||
|
[testenv:venv]
|
||||||
|
commands = {posargs}
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
ignore = E12
|
||||||
|
builtins = _
|
||||||
|
exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools
|
Loading…
Reference in New Issue
Block a user