Add fuel_package_updates package
New utility packages from a specified repo and optionally configures it for a specified Fuel environment. Added fuel_package_updates to flake8 tests blueprint package-fuel-components Change-Id: I2d6d1f0d3823fe53b995d710ebce190ac1dab0b5
This commit is contained in:
parent
d4bef41e2e
commit
df1cfd821a
|
@ -0,0 +1 @@
|
|||
include *requirements.txt
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright 2014 Mirantis, Inc.
|
||||
#
|
||||
# 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.
|
|
@ -0,0 +1,507 @@
|
|||
#!/usr/bin/python
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
from copy import deepcopy
|
||||
import functools
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
import urllib2
|
||||
import yaml
|
||||
import zlib
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except Exception:
|
||||
# python 2.6 or earlier use backport
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient.v2_0 import Client as keystoneclient
|
||||
|
||||
from optparse import OptionParser
|
||||
from urllib2 import urlopen
|
||||
from urlparse import urlparse
|
||||
from xml.dom.minidom import parseString
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
KEYSTONE_CREDS = {'username': os.environ.get('KEYSTONE_USERNAME', 'admin'),
|
||||
'password': os.environ.get('KEYSTONE_PASSWORD', 'admin'),
|
||||
'tenant_name': os.environ.get('KEYSTONE_TENANT', 'admin')}
|
||||
|
||||
|
||||
class Settings(object):
|
||||
supported_distros = ('centos', 'ubuntu',)
|
||||
supported_releases = ('2014.2-6.1', )
|
||||
updates_destinations = {
|
||||
'centos': r'/var/www/nailgun/{0}/centos/updates',
|
||||
'ubuntu': r'/var/www/nailgun/{0}/ubuntu/updates',
|
||||
}
|
||||
exclude_dirs = ('repodata/', 'mos?.?/')
|
||||
httproot = "/var/www/nailgun"
|
||||
port = 8000
|
||||
|
||||
|
||||
class HTTPClient(object):
|
||||
|
||||
def __init__(self, url, keystone_url, credentials, **kwargs):
|
||||
logger.debug('Initiate HTTPClient with url %s', url)
|
||||
self.url = url
|
||||
self.keystone_url = keystone_url
|
||||
self.creds = dict(credentials, **kwargs)
|
||||
self.keystone = None
|
||||
self.opener = urllib2.build_opener(urllib2.HTTPHandler)
|
||||
|
||||
def authenticate(self):
|
||||
try:
|
||||
logger.debug('Initialize keystoneclient with url %s',
|
||||
self.keystone_url)
|
||||
self.keystone = keystoneclient(
|
||||
auth_url=self.keystone_url, **self.creds)
|
||||
# it depends on keystone version, some versions doing auth
|
||||
# explicitly some dont, but we are making it explicitly always
|
||||
self.keystone.authenticate()
|
||||
logger.debug('Authorization token is successfully updated')
|
||||
except exceptions.AuthorizationFailure:
|
||||
logger.warning(
|
||||
'Cant establish connection to keystone with url %s',
|
||||
self.keystone_url)
|
||||
|
||||
@property
|
||||
def token(self):
|
||||
if self.keystone is not None:
|
||||
try:
|
||||
return self.keystone.auth_token
|
||||
except exceptions.AuthorizationFailure:
|
||||
logger.warning(
|
||||
'Cant establish connection to keystone with url %s',
|
||||
self.keystone_url)
|
||||
except exceptions.Unauthorized:
|
||||
logger.warning("Keystone returned unauthorized error, trying "
|
||||
"to pass authentication.")
|
||||
self.authenticate()
|
||||
return self.keystone.auth_token
|
||||
return None
|
||||
|
||||
def get(self, endpoint):
|
||||
req = urllib2.Request(self.url + endpoint)
|
||||
return self._open(req)
|
||||
|
||||
def post(self, endpoint, data=None, content_type="application/json"):
|
||||
if not data:
|
||||
data = {}
|
||||
logger.info('self url is %s' % self.url)
|
||||
req = urllib2.Request(self.url + endpoint, data=json.dumps(data))
|
||||
req.add_header('Content-Type', content_type)
|
||||
return self._open(req)
|
||||
|
||||
def put(self, endpoint, data=None, content_type="application/json"):
|
||||
if not data:
|
||||
data = {}
|
||||
req = urllib2.Request(self.url + endpoint, data=json.dumps(data))
|
||||
req.add_header('Content-Type', content_type)
|
||||
req.get_method = lambda: 'PUT'
|
||||
return self._open(req)
|
||||
|
||||
def delete(self, endpoint):
|
||||
req = urllib2.Request(self.url + endpoint)
|
||||
req.get_method = lambda: 'DELETE'
|
||||
return self._open(req)
|
||||
|
||||
def _open(self, req):
|
||||
try:
|
||||
return self._get_response(req)
|
||||
except urllib2.HTTPError as e:
|
||||
if e.code == 401:
|
||||
logger.warning('Authorization failure: {0}'.format(e.read()))
|
||||
self.authenticate()
|
||||
return self._get_response(req)
|
||||
else:
|
||||
raise
|
||||
|
||||
def _get_response(self, req):
|
||||
if self.token is not None:
|
||||
try:
|
||||
logger.debug('Set X-Auth-Token to {0}'.format(self.token))
|
||||
req.add_header("X-Auth-Token", self.token)
|
||||
except exceptions.AuthorizationFailure:
|
||||
logger.warning('Failed with auth in http _get_response')
|
||||
logger.warning(traceback.format_exc())
|
||||
return self.opener.open(req)
|
||||
|
||||
|
||||
def repo_merge(a, b):
|
||||
'''merges two lists of repositories. b replaces records from a.'''
|
||||
if not isinstance(b, list):
|
||||
return deepcopy(b)
|
||||
result = OrderedDict()
|
||||
for repo in a:
|
||||
result[repo['name']] = repo
|
||||
for repo in b:
|
||||
result[repo['name']] = repo
|
||||
return result.values()
|
||||
|
||||
|
||||
class FuelWebClient(object):
|
||||
|
||||
def __init__(self, admin_node_ip):
|
||||
self.admin_node_ip = admin_node_ip
|
||||
self.client = NailgunClient(admin_node_ip)
|
||||
super(FuelWebClient, self).__init__()
|
||||
|
||||
def environment(self):
|
||||
"""Environment Model
|
||||
:rtype: EnvironmentModel
|
||||
"""
|
||||
return self._environment
|
||||
|
||||
def update_cluster_repos(self,
|
||||
cluster_id,
|
||||
settings=None):
|
||||
"""Updates a cluster with new settings
|
||||
:param cluster_id:
|
||||
:param settings:
|
||||
"""
|
||||
|
||||
if settings is None:
|
||||
settings = {}
|
||||
|
||||
attributes = self.client.get_cluster_attributes(cluster_id)
|
||||
|
||||
if 'repo_setup' in attributes['editable']:
|
||||
repos_attr = attributes['editable']['repo_setup']['repos']
|
||||
repos_attr['value'] = repo_merge(repos_attr['value'], settings)
|
||||
|
||||
logger.debug("Try to update cluster "
|
||||
"with next attributes {0}".format(attributes))
|
||||
self.client.update_cluster_attributes(cluster_id, attributes)
|
||||
|
||||
|
||||
class NailgunClient(object):
|
||||
def __init__(self, admin_node_ip, **kwargs):
|
||||
url = "http://{0}:8000".format(admin_node_ip)
|
||||
logger.debug('Initiate Nailgun client with url %s', url)
|
||||
self.keystone_url = "http://{0}:5000/v2.0".format(admin_node_ip)
|
||||
self._client = HTTPClient(url=url, keystone_url=self.keystone_url,
|
||||
credentials=KEYSTONE_CREDS,
|
||||
**kwargs)
|
||||
super(NailgunClient, self).__init__()
|
||||
|
||||
def json_parse(func):
|
||||
@functools.wraps(func)
|
||||
def wrapped(*args, **kwargs):
|
||||
response = func(*args, **kwargs)
|
||||
return json.loads(response.read())
|
||||
return wrapped
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
return self._client
|
||||
|
||||
@json_parse
|
||||
def get_cluster_attributes(self, cluster_id):
|
||||
return self.client.get(
|
||||
"/api/clusters/{0}/attributes/".format(cluster_id)
|
||||
)
|
||||
|
||||
@json_parse
|
||||
def update_cluster_attributes(self, cluster_id, attrs):
|
||||
return self.client.put(
|
||||
"/api/clusters/{0}/attributes/".format(cluster_id),
|
||||
attrs
|
||||
)
|
||||
|
||||
|
||||
class UpdatePackagesException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def exec_cmd(cmd):
|
||||
logger.debug('Execute command "%s"', cmd)
|
||||
child = subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
shell=True)
|
||||
|
||||
logger.debug('Stdout and stderr of command "%s":', cmd)
|
||||
for line in child.stdout:
|
||||
logger.debug(line.rstrip())
|
||||
|
||||
return _wait_and_check_exit_code(cmd, child)
|
||||
|
||||
|
||||
def _wait_and_check_exit_code(cmd, child):
|
||||
child.wait()
|
||||
exit_code = child.returncode
|
||||
logger.debug('Command "%s" was executed', cmd)
|
||||
return exit_code
|
||||
|
||||
|
||||
def get_repository_packages(remote_repo_url, distro):
|
||||
repo_url = urlparse(remote_repo_url)
|
||||
packages = []
|
||||
if distro in ('ubuntu',):
|
||||
packages_url = '{0}/Packages'.format(repo_url.geturl())
|
||||
pkgs_raw = urlopen(packages_url).read()
|
||||
for pkg in pkgs_raw.split('\n'):
|
||||
match = re.search(r'^Package: (\S+)\s*$', pkg)
|
||||
if match:
|
||||
packages.append(match.group(1))
|
||||
elif distro in ('centos',):
|
||||
packages_url = '{0}/repodata/primary.xml.gz'.format(repo_url.geturl())
|
||||
pkgs_xml = parseString(zlib.decompressobj(zlib.MAX_WBITS | 32).
|
||||
decompress(urlopen(packages_url).read()))
|
||||
for pkg in pkgs_xml.getElementsByTagName('package'):
|
||||
packages.append(
|
||||
pkg.getElementsByTagName('name')[0].firstChild.nodeValue)
|
||||
return packages
|
||||
|
||||
|
||||
def get_ubuntu_repos(repopath, ip, httproot, port, baseurl=None):
|
||||
# TODO(mattymo): parse all repo metadata
|
||||
repolist = ['mos6.1-updates', 'mos6.1-security', 'mos6.1-holdback']
|
||||
if baseurl:
|
||||
repourl = "{baseurl}/{repopath}".format(
|
||||
baseurl=baseurl,
|
||||
repopath=repopath.replace(httproot, ''))
|
||||
else:
|
||||
repourl = "http://{ip}:{port}/{repopath}".format(
|
||||
ip=ip,
|
||||
port=port,
|
||||
repopath=repopath.replace(httproot, ''))
|
||||
|
||||
repos = []
|
||||
for repo in repolist:
|
||||
# FIXME(mattymo): repositories cannot have a period in their name
|
||||
name = repo.replace('6.1', '')
|
||||
repoentry = {
|
||||
"type": "deb",
|
||||
"name": name,
|
||||
"uri": repourl,
|
||||
"suite": repo,
|
||||
"section": "main restricted",
|
||||
"priority": 1050}
|
||||
if "holdback" in repo:
|
||||
repoentry['priority'] = 1100
|
||||
repos.append(repoentry)
|
||||
return repos
|
||||
|
||||
|
||||
def get_centos_repos(repopath, ip, httproot, port, baseurl=None):
|
||||
if baseurl:
|
||||
repourl = "{baseurl}/{repopath}".format(
|
||||
baseurl=baseurl,
|
||||
repopath=repopath.replace(httproot, ''))
|
||||
else:
|
||||
repourl = "http://{ip}:{port}/{repopath}".format(
|
||||
ip=ip,
|
||||
port=port,
|
||||
repopath=repopath.replace(httproot, ''))
|
||||
|
||||
repoentry = {
|
||||
"type": "rpm",
|
||||
"name": "MOS-Updates",
|
||||
"uri": repourl,
|
||||
"priority": 20}
|
||||
return [repoentry]
|
||||
|
||||
|
||||
def reindent(s, numSpaces):
|
||||
s = string.split(s, '\n')
|
||||
s = [(numSpaces * ' ') + line for line in s]
|
||||
s = string.join(s, '\n')
|
||||
return s
|
||||
|
||||
|
||||
def show_env_conf(repos, showuri=False, ip="10.20.0.2"):
|
||||
print("Your repositories are now ready for use. You will need to update "
|
||||
"your Fuel environment configuration to use these repositories.")
|
||||
print("Note: Be sure to replace ONLY the repositories listed below.\n")
|
||||
if not showuri:
|
||||
print("Replace the entire repos section of your environment using "
|
||||
"the following commands:\n fuel --env 1 env --attributes "
|
||||
"--download\n vim cluster_1/attributes.yaml\n fuel --env "
|
||||
"1 env --attributes --upload")
|
||||
|
||||
if showuri:
|
||||
for repo in repos:
|
||||
if repo['type'] == "deb":
|
||||
print("{name}:\ndeb {uri} {suite} {section}".format(
|
||||
name=repo['name'],
|
||||
uri=repo['uri'],
|
||||
suite=repo['suite'],
|
||||
section=repo['section']))
|
||||
else:
|
||||
print("{name}:\n{uri}".format(
|
||||
name=repo['name'],
|
||||
uri=repo['uri']))
|
||||
else:
|
||||
spaces = 10
|
||||
yamldata = {"repos": repos}
|
||||
print(reindent(yaml.dump(yamldata, default_flow_style=False), spaces))
|
||||
|
||||
|
||||
def update_env_conf(ip, env_id, distro, repos):
|
||||
fwc = FuelWebClient(ip)
|
||||
fwc.update_cluster_repos(env_id, repos)
|
||||
|
||||
|
||||
def mirror_remote_repository(remote_repo_url, local_repo_path, exclude_dirs,
|
||||
distro):
|
||||
repo_url = urlparse(remote_repo_url)
|
||||
cut_dirs = len(repo_url.path.strip('/').split('/'))
|
||||
if "rsync://" in remote_repo_url:
|
||||
excl_dirs = "ubuntu/dists/mos?.?/,repodata/"
|
||||
download_cmd = ('rsync --exclude="*.key","*.gpg",{excl_dirs} -vPr '
|
||||
'{url} {path}').format(pwd=repo_url.path.rstrip('/'),
|
||||
path=local_repo_path,
|
||||
excl_dirs=excl_dirs,
|
||||
url=repo_url.geturl())
|
||||
else:
|
||||
excl_dirs = "--exclude-directories='ubuntu/dists/mos?.?/,repodata'"
|
||||
download_cmd = (
|
||||
'wget --recursive --no-parent --no-verbose -R "*.html" -R '
|
||||
'"*.gif" -R "*.key" -R "*.gpg" -R "*.dsc" -R "*.tar.gz" '
|
||||
'{excl_dirs} --directory-prefix {path} -nH '
|
||||
'--cut-dirs={cutd} '
|
||||
'{url}').format(pwd=repo_url.path.rstrip('/'),
|
||||
excl_dirs=excl_dirs,
|
||||
path=local_repo_path,
|
||||
cutd=cut_dirs,
|
||||
url=repo_url.geturl())
|
||||
|
||||
logger.debug('Execute command "%s"', download_cmd)
|
||||
if exec_cmd(download_cmd) != 0:
|
||||
raise UpdatePackagesException('Mirroring of remote packages'
|
||||
' repository failed!')
|
||||
|
||||
|
||||
def main():
|
||||
settings = Settings()
|
||||
|
||||
sh = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||
sh.setFormatter(formatter)
|
||||
logger.addHandler(sh)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
parser = OptionParser(
|
||||
description="Pull updates for a given release of Fuel based on "
|
||||
"the provided URL."
|
||||
)
|
||||
parser.add_option('-l', '--list-distros', dest='list_distros',
|
||||
default=None, action="store_true",
|
||||
help='List available distributions.')
|
||||
parser.add_option('-d', '--distro', dest='distro', default=None,
|
||||
help='Distribution name (required)')
|
||||
parser.add_option('-r', '--release', dest='release', default=None,
|
||||
help='Fuel release name (required)')
|
||||
parser.add_option("-u", "--url", dest="url", default="",
|
||||
help="Remote repository URL (required)")
|
||||
parser.add_option("-v", "--verbose",
|
||||
action="store_true", dest="verbose", default=False,
|
||||
help="Enable debug output")
|
||||
parser.add_option("-i", "--show-uris", dest="showuri", default=False,
|
||||
action="store_true",
|
||||
help="Show URIs for new repositories (optional). "
|
||||
"Useful for WebUI.")
|
||||
parser.add_option("-a", "--apply", dest="apply", default=False,
|
||||
action="store_true",
|
||||
help="Apply changes to Fuel environment (optional)")
|
||||
parser.add_option("-e", "--env", dest="env", default=None,
|
||||
help="Fuel environment ID (required for option -a)")
|
||||
parser.add_option("-s", "--fuel-server", dest="ip", default="10.20.0.2",
|
||||
help="Address of Fuel Master public address (defaults "
|
||||
"to 10.20.0.2)")
|
||||
parser.add_option("-b", "--baseurl", dest="baseurl", default=None,
|
||||
help="URL prefix for mirror, such as http://myserver."
|
||||
"company.com/repos (optional)")
|
||||
parser.add_option("-p", "--password", dest="admin_pass", default=None,
|
||||
help="Fuel Master admin password (defaults to admin)."
|
||||
" Alternatively, use env var KEYSTONE_PASSWORD).")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if options.verbose:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
if options.list_distros:
|
||||
logger.info("Available distributions:\n {0}".format(
|
||||
"\n ".join(settings.supported_distros)))
|
||||
sys.exit(0)
|
||||
|
||||
if options.distro not in settings.supported_distros:
|
||||
raise UpdatePackagesException(
|
||||
'Distro "{0}" is not supported. Please specify one of the '
|
||||
'following: "{1}". See help (--help) for details.'.format(
|
||||
options.distro, ', '.join(settings.supported_distros)))
|
||||
|
||||
if options.release not in settings.supported_releases:
|
||||
raise UpdatePackagesException(
|
||||
'Fuel release "{0}" is not supported. Please specify one of the '
|
||||
'following: "{1}". See help (--help) for details.'.format(
|
||||
options.release, ', '.join(settings.supported_releases)))
|
||||
|
||||
if 'http' not in urlparse(options.url) and 'rsync' not in \
|
||||
urlparse(options.url):
|
||||
raise UpdatePackagesException(
|
||||
'Repository url "{0}" does not look like valid URL. '
|
||||
'See help (--help) for details.'.format(options.url))
|
||||
|
||||
if options.apply and not options.env:
|
||||
raise UpdatePackagesException(
|
||||
'--apply option requires --env to be specified. '
|
||||
'See help (--help) for details.')
|
||||
|
||||
updates_path = settings.updates_destinations[options.distro].format(
|
||||
options.release)
|
||||
if not os.path.exists(updates_path):
|
||||
os.makedirs(updates_path)
|
||||
logger.info('Started mirroring remote repository...')
|
||||
mirror_remote_repository(options.url, updates_path,
|
||||
settings.exclude_dirs, options.distro)
|
||||
logger.info('Remote repository "{url}" for "{release}" ({distro}) was '
|
||||
'successfuly mirrored to {path} folder.'.format(
|
||||
url=options.url,
|
||||
release=options.release,
|
||||
distro=options.distro,
|
||||
path=updates_path))
|
||||
if options.distro == "ubuntu":
|
||||
repos = get_ubuntu_repos(updates_path, options.ip, settings.httproot,
|
||||
settings.port, options.baseurl)
|
||||
elif options.distro == "centos":
|
||||
repos = get_centos_repos(updates_path, options.ip, settings.httproot,
|
||||
settings.port, options.baseurl)
|
||||
else:
|
||||
raise UpdatePackagesException('Unknown distro "{0}"'.format(
|
||||
options.distro))
|
||||
if options.admin_pass:
|
||||
KEYSTONE_CREDS['password'] = options.admin_pass
|
||||
if options.apply:
|
||||
update_env_conf(options.ip, options.env, options.distro, repos)
|
||||
else:
|
||||
show_env_conf(repos, options.showuri, options.ip)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,3 @@
|
|||
ordereddict>=1.1
|
||||
PyYAML==3.10
|
||||
python-keystoneclient
|
|
@ -0,0 +1,49 @@
|
|||
# Copyright 2014 Mirantis, Inc.
|
||||
#
|
||||
# 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 os.path
|
||||
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
def find_requires():
|
||||
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||
requirements = []
|
||||
with open(u'{0}/requirements.txt'.format(dir_path), 'r') as reqs:
|
||||
requirements = reqs.readlines()
|
||||
return requirements
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
setup(name='fuel_package_updates',
|
||||
version='6.0.0',
|
||||
description='Package update downloader for Fuel Master node',
|
||||
long_description='Package Update downloader for Fuel Master node',
|
||||
classifiers=[
|
||||
"Programming Language :: Python",
|
||||
"Topic :: System :: Software Distribution"],
|
||||
author='Mirantis Inc.',
|
||||
author_email='product@mirantis.com',
|
||||
url='http://mirantis.com',
|
||||
keywords='fuel update mirantis',
|
||||
packages=find_packages(),
|
||||
zip_safe=False,
|
||||
install_requires=find_requires(),
|
||||
include_package_data=True,
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'fuel-package-updates = fuel_package_updates.fuel_package_'
|
||||
'updates:main']})
|
|
@ -0,0 +1,6 @@
|
|||
-r requirements.txt
|
||||
hacking==0.10.1
|
||||
mock==1.0
|
||||
nose==1.1.2
|
||||
nose2==0.4.1
|
||||
nose-timer==0.2.0
|
|
@ -0,0 +1,38 @@
|
|||
[tox]
|
||||
minversion = 1.6
|
||||
skipsdist = True
|
||||
envlist = py26,py27,pep8
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
install_command = pip install --allow-external -U {opts} {packages}
|
||||
setenv = VIRTUAL_ENV={envdir}
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
commands =
|
||||
nosetests {posargs:fuel_package_updates}
|
||||
|
||||
[tox:jenkins]
|
||||
downloadcache = ~/cache/pip
|
||||
|
||||
[testenv:pep8]
|
||||
deps = hacking==0.7
|
||||
usedevelop = False
|
||||
commands =
|
||||
flake8 {posargs:.}
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs:}
|
||||
|
||||
[testenv:devenv]
|
||||
envdir = devenv
|
||||
usedevelop = True
|
||||
|
||||
[flake8]
|
||||
ignore = H234,H302,H802
|
||||
exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools,__init__.py,docs
|
||||
show-pep8 = True
|
||||
show-source = True
|
||||
count = True
|
||||
|
||||
[hacking]
|
||||
import_exceptions = testtools.matchers
|
|
@ -419,6 +419,7 @@ function run_flake8 {
|
|||
run_flake8_subproject fuelmenu && \
|
||||
run_flake8_subproject network_checker && \
|
||||
run_flake8_subproject fuel_upgrade_system/fuel_upgrade && \
|
||||
run_flake8_subproject fuel_upgrade_system/fuel_package_updates && \
|
||||
run_flake8_subproject fuel_development && \
|
||||
run_flake8_subproject shotgun || result=1
|
||||
return $result
|
||||
|
|
Loading…
Reference in New Issue