Implement /prices by extracting price from odoo
Change-Id: Ie1a39253fe1f722ccce79cf2657fa4e5a0bc8eaa
This commit is contained in:
parent
0d20c80a6e
commit
28fb58703e
@ -32,4 +32,14 @@ def prices_get():
|
|||||||
|
|
||||||
@rest.get('/costs')
|
@rest.get('/costs')
|
||||||
def costs_get():
|
def costs_get():
|
||||||
return api.render(costs=costs.get_costs())
|
return api.render(costs=costs.get_costs())
|
||||||
|
|
||||||
|
|
||||||
|
@rest.get('/usages')
|
||||||
|
def usages_get():
|
||||||
|
return api.render(usages={})
|
||||||
|
|
||||||
|
|
||||||
|
@rest.get('/health')
|
||||||
|
def health_get():
|
||||||
|
return api.render(health={})
|
||||||
|
@ -23,7 +23,7 @@ from sqlalchemy import create_engine
|
|||||||
from sqlalchemy import pool
|
from sqlalchemy import pool
|
||||||
|
|
||||||
from distil.db.sqlalchemy import model_base
|
from distil.db.sqlalchemy import model_base
|
||||||
from distil.openstack.common import importutils
|
from oslo_utils import importutils
|
||||||
|
|
||||||
|
|
||||||
importutils.import_module('distil.db.sqlalchemy.models')
|
importutils.import_module('distil.db.sqlalchemy.models')
|
||||||
|
@ -100,16 +100,9 @@ def upgrade():
|
|||||||
# mysql_engine=MYSQL_ENGINE,
|
# mysql_engine=MYSQL_ENGINE,
|
||||||
# mysql_charset=MYSQL_CHARSET)
|
# mysql_charset=MYSQL_CHARSET)
|
||||||
|
|
||||||
op.create_table('last_run',
|
|
||||||
sa.Column('id', sa.Integer, primary_key=True,
|
|
||||||
sa.Sequence("last_run_id_seq")),
|
|
||||||
sa.Column('last_run', sa.DateTime(), nullable=True),
|
|
||||||
mysql_engine=MYSQL_ENGINE,
|
|
||||||
mysql_charset=MYSQL_CHARSET)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
op.drop_table('project')
|
op.drop_table('project')
|
||||||
op.drop_table('usage')
|
op.drop_table('usage')
|
||||||
op.drop_table('resource')
|
op.drop_table('resource')
|
||||||
op.drop_table('last_run')
|
|
@ -13,15 +13,15 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from oslo.utils import timeutils
|
from oslo_utils import timeutils
|
||||||
from oslo.db.sqlalchemy import models as oslo_models
|
from oslo_db.sqlalchemy import models as oslo_models
|
||||||
from sqlalchemy import Column
|
from sqlalchemy import Column
|
||||||
from sqlalchemy import DateTime
|
from sqlalchemy import DateTime
|
||||||
from sqlalchemy.ext import declarative
|
from sqlalchemy.ext import declarative
|
||||||
from sqlalchemy import Text
|
from sqlalchemy import Text
|
||||||
from sqlalchemy.types import TypeDecorator
|
from sqlalchemy.types import TypeDecorator
|
||||||
|
|
||||||
from distil.openstack.common import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
|
|
||||||
class JSONEncodedDict(TypeDecorator):
|
class JSONEncodedDict(TypeDecorator):
|
||||||
|
@ -14,12 +14,24 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from distil import rater
|
from distil import rater
|
||||||
|
from distil.rater import rate_file
|
||||||
from distil.utils import odoo
|
from distil.utils import odoo
|
||||||
|
|
||||||
class OdooRater(rater.BaseRater):
|
class OdooRater(rater.BaseRater):
|
||||||
|
|
||||||
|
def __init__(self, conf):
|
||||||
|
self.prices = odoo.Odoo().get_prices()
|
||||||
|
|
||||||
def rate(self, name, region=None):
|
def rate(self, name, region=None):
|
||||||
erp = odoo.Odoo()
|
if not self.prices:
|
||||||
import pdb
|
return rate_file.FileRater().rate(name, region)
|
||||||
pdb.set_trace()
|
|
||||||
pass
|
for region in self.prices:
|
||||||
|
for category in self.prices[region]:
|
||||||
|
for product in self.prices[region][category]:
|
||||||
|
if product == name:
|
||||||
|
return {'rate': self.prices[name]['price'],
|
||||||
|
'unit': self.prices[name]['unit']
|
||||||
|
}
|
||||||
|
|
||||||
|
return rate_file.FileRater().rate(name, region)
|
||||||
|
0
distil/service/api/v2/__init__.py
Normal file
0
distil/service/api/v2/__init__.py
Normal file
23
distil/service/api/v2/costs.py
Normal file
23
distil/service/api/v2/costs.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Copyright (c) 2016 Catalyst IT Ltd.
|
||||||
|
#
|
||||||
|
# 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 oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
def get_costs():
|
||||||
|
return {'id': 1}
|
@ -21,9 +21,5 @@ LOG = logging.getLogger(__name__)
|
|||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
def get_prices(format=None):
|
def get_prices(format=None):
|
||||||
import pdb
|
prices = odoo.Odoo().get_prices()
|
||||||
pdb.set_trace()
|
return prices
|
||||||
erp = odoo.Odoo()
|
|
||||||
erp.get_prices()
|
|
||||||
pdb.set_trace()
|
|
||||||
return {'id': 1}
|
|
84
distil/utils/odoo.py
Normal file
84
distil/utils/odoo.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# Copyright (c) 2016 Catalyst IT Ltd.
|
||||||
|
#
|
||||||
|
# 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 odoorpc
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
PRODUCT_CATEGORY = ('Compute', 'Network', 'Block Storage', 'Object Storage')
|
||||||
|
REGION_MAPPING = {'nz-por-1': 'NZ-POR-1', 'nz_wlg_2': 'NZ-WLG-2'}
|
||||||
|
|
||||||
|
class Odoo(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.odoo = odoorpc.ODOO(CONF.odoo.hostname,
|
||||||
|
protocol=CONF.odoo.protocol,
|
||||||
|
port=CONF.odoo.port,
|
||||||
|
version=CONF.odoo.version)
|
||||||
|
|
||||||
|
self.odoo.login(CONF.odoo.database, CONF.odoo.user, CONF.odoo.password)
|
||||||
|
|
||||||
|
self.order = self.odoo.env['sale.order']
|
||||||
|
self.orderline = self.odoo.env['sale.order.line']
|
||||||
|
self.tenant = self.odoo.env['cloud.tenant']
|
||||||
|
self.partner = self.odoo.env['res.partner']
|
||||||
|
self.pricelist = self.odoo.env['product.pricelist']
|
||||||
|
self.product = self.odoo.env['product.product']
|
||||||
|
self.category = self.odoo.env['product.category']
|
||||||
|
|
||||||
|
def get_prices(self, region=None):
|
||||||
|
# TODO(flwang): Need to cache the prices, now generally this method
|
||||||
|
# will take 30+ seconds to get the two regions prices.
|
||||||
|
if region:
|
||||||
|
regions = [REGION_MAPPING[region.lower()]]
|
||||||
|
else:
|
||||||
|
regions = REGION_MAPPING.values()
|
||||||
|
|
||||||
|
prices = {}
|
||||||
|
for r in regions:
|
||||||
|
prices[r] = {}
|
||||||
|
for category in PRODUCT_CATEGORY:
|
||||||
|
prices[r][category.lower()] = {}
|
||||||
|
c = self.category.search([
|
||||||
|
('name', '=', category),
|
||||||
|
('display_name', 'ilike', r)
|
||||||
|
])
|
||||||
|
product_ids = self.product.search([
|
||||||
|
('categ_id', '=', c[0]),
|
||||||
|
('sale_ok', '=', True),
|
||||||
|
('active', '=', True)
|
||||||
|
])
|
||||||
|
products = self.odoo.execute('product.product',
|
||||||
|
'read',
|
||||||
|
product_ids)
|
||||||
|
for p in products:
|
||||||
|
name = p['name_template'][len(r)+1:]
|
||||||
|
price = round(p['lst_price'], 5)
|
||||||
|
# NOTE(flwang): default_code is Internal Reference on Odoo
|
||||||
|
# GUI
|
||||||
|
unit = p['default_code']
|
||||||
|
desc = p['description']
|
||||||
|
prices[r][category.lower()][name] = {'price': price,
|
||||||
|
'unit': unit,
|
||||||
|
'description': desc
|
||||||
|
}
|
||||||
|
|
||||||
|
return prices
|
||||||
|
|
||||||
|
def get_customers(self):
|
||||||
|
pass
|
@ -1,81 +0,0 @@
|
|||||||
#!/usr/bin/env python2
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import pprint
|
|
||||||
import argparse
|
|
||||||
import math
|
|
||||||
import ConfigParser
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
import odoorpc
|
|
||||||
|
|
||||||
# requires distilclient>=0.5.1
|
|
||||||
from distilclient.client import Client as DistilClient
|
|
||||||
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('--start', required=True, help='Start date')
|
|
||||||
parser.add_argument('--end', required=True, help='End date')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
conf = ConfigParser.ConfigParser()
|
|
||||||
conf.read(['glue.ini'])
|
|
||||||
|
|
||||||
region = conf.get('openstack', 'region')
|
|
||||||
|
|
||||||
oerp = odoorpc.ODOO(
|
|
||||||
conf.get('odoo', 'hostname'),
|
|
||||||
protocol=conf.get('odoo', 'protocol'),
|
|
||||||
port=conf.getint('odoo', 'port'),
|
|
||||||
version=conf.get('odoo', 'version')
|
|
||||||
)
|
|
||||||
oerp.login(
|
|
||||||
conf.get('odoo', 'database'),
|
|
||||||
conf.get('odoo', 'user'),
|
|
||||||
conf.get('odoo', 'password')
|
|
||||||
)
|
|
||||||
|
|
||||||
# debug helper
|
|
||||||
def dump_all(model, fields, conds=None):
|
|
||||||
print '%s:' % model
|
|
||||||
ids = oerp.search(model, conds or [])
|
|
||||||
objs = oerp.read(model, ids)
|
|
||||||
for obj in objs:
|
|
||||||
print ' %s %s' % (obj['id'], {f:obj[f] for f in fields})
|
|
||||||
|
|
||||||
pricelist_model = oerp.env['product.pricelist']
|
|
||||||
pricelist = oerp.search('product.pricelist',
|
|
||||||
[('name', '=', conf.get('odoo', 'export_pricelist'))])
|
|
||||||
|
|
||||||
product_category = oerp.search('product.category',
|
|
||||||
[('name', '=', conf.get('odoo', 'product_category'))])
|
|
||||||
|
|
||||||
product_ids = oerp.search('product.product',
|
|
||||||
[('categ_id', '=', product_category[0]),
|
|
||||||
('sale_ok', '=', True),
|
|
||||||
('active', '=', True)])
|
|
||||||
|
|
||||||
products = oerp.read('product.product', product_ids)
|
|
||||||
|
|
||||||
prices = {}
|
|
||||||
|
|
||||||
for p in products:
|
|
||||||
if not p['name_template'].startswith(region + '.'):
|
|
||||||
continue
|
|
||||||
base_name = p['name_template'][len(region)+1:]
|
|
||||||
# exported prices are for one unit -- do not take into account
|
|
||||||
# any bulk pricing rules.
|
|
||||||
unit_price = pricelist_model.price_get([pricelist[0]], p['id'], 1)[str(pricelist[0])]
|
|
||||||
prices[base_name] = unit_price
|
|
||||||
print '%s %s' % (base_name, unit_price)
|
|
||||||
|
|
||||||
# create the snapshot in distil
|
|
||||||
dc = DistilClient(
|
|
||||||
os_username=os.getenv('OS_USERNAME'),
|
|
||||||
os_password=os.getenv('OS_PASSWORD'),
|
|
||||||
os_tenant_id=os.getenv('OS_TENANT_ID'),
|
|
||||||
os_auth_url=os.getenv('OS_AUTH_URL'),
|
|
||||||
os_region_name=os.getenv('OS_REGION_NAME'))
|
|
||||||
|
|
||||||
dc.set_prices(args.start, args.end, prices)
|
|
25
tools/config/check_uptodate.sh
Normal file
25
tools/config/check_uptodate.sh
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
PROJECT_NAME=${PROJECT_NAME:-wellington}
|
||||||
|
CFGFILE_NAME=${PROJECT_NAME}.conf.sample
|
||||||
|
|
||||||
|
if [ -e etc/${PROJECT_NAME}/${CFGFILE_NAME} ]; then
|
||||||
|
CFGFILE=etc/${PROJECT_NAME}/${CFGFILE_NAME}
|
||||||
|
elif [ -e etc/${CFGFILE_NAME} ]; then
|
||||||
|
CFGFILE=etc/${CFGFILE_NAME}
|
||||||
|
else
|
||||||
|
echo "${0##*/}: can not find config file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TEMPDIR=`mktemp -d /tmp/${PROJECT_NAME}.XXXXXX`
|
||||||
|
trap "rm -rf $TEMPDIR" EXIT
|
||||||
|
|
||||||
|
tools/config/generate_sample.sh -b ./ -p ${PROJECT_NAME} -o ${TEMPDIR}
|
||||||
|
|
||||||
|
if ! diff -u ${TEMPDIR}/${CFGFILE_NAME} ${CFGFILE}
|
||||||
|
then
|
||||||
|
echo "${0##*/}: ${PROJECT_NAME}.conf.sample is not up to date."
|
||||||
|
echo "${0##*/}: Please run ${0%%${0##*/}}generate_sample.sh."
|
||||||
|
exit 1
|
||||||
|
fi
|
119
tools/config/generate_sample.sh
Normal file
119
tools/config/generate_sample.sh
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
print_hint() {
|
||||||
|
echo "Try \`${0##*/} --help' for more information." >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
PARSED_OPTIONS=$(getopt -n "${0##*/}" -o hb:p:m:l:o: \
|
||||||
|
--long help,base-dir:,package-name:,output-dir:,module:,library: -- "$@")
|
||||||
|
|
||||||
|
if [ $? != 0 ] ; then print_hint ; exit 1 ; fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED_OPTIONS"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
echo "${0##*/} [options]"
|
||||||
|
echo ""
|
||||||
|
echo "options:"
|
||||||
|
echo "-h, --help show brief help"
|
||||||
|
echo "-b, --base-dir=DIR project base directory"
|
||||||
|
echo "-p, --package-name=NAME project package name"
|
||||||
|
echo "-o, --output-dir=DIR file output directory"
|
||||||
|
echo "-m, --module=MOD extra python module to interrogate for options"
|
||||||
|
echo "-l, --library=LIB extra library that registers options for discovery"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-b|--base-dir)
|
||||||
|
shift
|
||||||
|
BASEDIR=`echo $1 | sed -e 's/\/*$//g'`
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-p|--package-name)
|
||||||
|
shift
|
||||||
|
PACKAGENAME=`echo $1`
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-o|--output-dir)
|
||||||
|
shift
|
||||||
|
OUTPUTDIR=`echo $1 | sed -e 's/\/*$//g'`
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-m|--module)
|
||||||
|
shift
|
||||||
|
MODULES="$MODULES -m $1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-l|--library)
|
||||||
|
shift
|
||||||
|
LIBRARIES="$LIBRARIES -l $1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
BASEDIR=${BASEDIR:-`pwd`}
|
||||||
|
if ! [ -d $BASEDIR ]
|
||||||
|
then
|
||||||
|
echo "${0##*/}: missing project base directory" >&2 ; print_hint ; exit 1
|
||||||
|
elif [[ $BASEDIR != /* ]]
|
||||||
|
then
|
||||||
|
BASEDIR=$(cd "$BASEDIR" && pwd)
|
||||||
|
fi
|
||||||
|
|
||||||
|
PACKAGENAME=${PACKAGENAME:-${BASEDIR##*/}}
|
||||||
|
TARGETDIR=$BASEDIR/$PACKAGENAME
|
||||||
|
if ! [ -d $TARGETDIR ]
|
||||||
|
then
|
||||||
|
echo "${0##*/}: invalid project package name" >&2 ; print_hint ; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
OUTPUTDIR=${OUTPUTDIR:-$BASEDIR/etc}
|
||||||
|
# NOTE(bnemec): Some projects put their sample config in etc/,
|
||||||
|
# some in etc/$PACKAGENAME/
|
||||||
|
if [ -d $OUTPUTDIR/$PACKAGENAME ]
|
||||||
|
then
|
||||||
|
OUTPUTDIR=$OUTPUTDIR/$PACKAGENAME
|
||||||
|
elif ! [ -d $OUTPUTDIR ]
|
||||||
|
then
|
||||||
|
echo "${0##*/}: cannot access \`$OUTPUTDIR': No such file or directory" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BASEDIRESC=`echo $BASEDIR | sed -e 's/\//\\\\\//g'`
|
||||||
|
find $TARGETDIR -type f -name "*.pyc" -delete
|
||||||
|
FILES=$(find $TARGETDIR -type f -name "*.py" ! -path "*/tests/*" \
|
||||||
|
-exec grep -l "Opt(" {} + | sed -e "s/^$BASEDIRESC\///g" | sort -u)
|
||||||
|
|
||||||
|
RC_FILE="`dirname $0`/oslo.config.generator.rc"
|
||||||
|
if test -r "$RC_FILE"
|
||||||
|
then
|
||||||
|
source "$RC_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for mod in ${WELLINGTON_CONFIG_GENERATOR_EXTRA_MODULES}; do
|
||||||
|
MODULES="$MODULES -m $mod"
|
||||||
|
done
|
||||||
|
|
||||||
|
for lib in ${WELLINGTON_CONFIG_GENERATOR_EXTRA_LIBRARIES}; do
|
||||||
|
LIBRARIES="$LIBRARIES -l $lib"
|
||||||
|
done
|
||||||
|
|
||||||
|
export EVENTLET_NO_GREENDNS=yes
|
||||||
|
|
||||||
|
OS_VARS=$(set | sed -n '/^OS_/s/=[^=]*$//gp' | xargs)
|
||||||
|
[ "$OS_VARS" ] && eval "unset \$OS_VARS"
|
||||||
|
DEFAULT_MODULEPATH=wellington.openstack.common.config.generator
|
||||||
|
MODULEPATH=${MODULEPATH:-$DEFAULT_MODULEPATH}
|
||||||
|
OUTPUTFILE=$OUTPUTDIR/$PACKAGENAME.conf.sample
|
||||||
|
python -m $MODULEPATH $MODULES $LIBRARIES $FILES > $OUTPUTFILE
|
||||||
|
|
||||||
|
# Hook to allow projects to append custom config file snippets
|
||||||
|
CONCAT_FILES=$(ls $BASEDIR/tools/config/*.conf.sample 2>/dev/null)
|
||||||
|
for CONCAT_FILE in $CONCAT_FILES; do
|
||||||
|
cat $CONCAT_FILE >> $OUTPUTFILE
|
||||||
|
done
|
1
tools/config/oslo.config.generator.rc
Normal file
1
tools/config/oslo.config.generator.rc
Normal file
@ -0,0 +1 @@
|
|||||||
|
export WELLINGTON_CONFIG_GENERATOR_EXTRA_MODULES="keystoneclient.middleware.auth_token"
|
3
tools/install_venv
Normal file
3
tools/install_venv
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
tox -vvv -evenv -- python --version
|
75
tools/install_venv.py
Normal file
75
tools/install_venv.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Copyright 2010 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Copyright 2010 OpenStack Foundation
|
||||||
|
# Copyright 2013 IBM Corp.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import ConfigParser
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import install_venv_common as install_venv # flake8: noqa
|
||||||
|
|
||||||
|
|
||||||
|
def print_help(project, venv, root):
|
||||||
|
help = """
|
||||||
|
%(project)s development environment setup is complete.
|
||||||
|
|
||||||
|
%(project)s development uses virtualenv to track and manage Python
|
||||||
|
dependencies while in development and testing.
|
||||||
|
|
||||||
|
To activate the %(project)s virtualenv for the extent of your current
|
||||||
|
shell session you can run:
|
||||||
|
|
||||||
|
$ source %(venv)s/bin/activate
|
||||||
|
|
||||||
|
Or, if you prefer, you can run commands in the virtualenv on a case by
|
||||||
|
case basis by running:
|
||||||
|
|
||||||
|
$ %(root)s/tools/with_venv.sh <your command>
|
||||||
|
"""
|
||||||
|
print help % dict(project=project, venv=venv, root=root)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
|
||||||
|
if os.environ.get('tools_path'):
|
||||||
|
root = os.environ['tools_path']
|
||||||
|
venv = os.path.join(root, '.venv')
|
||||||
|
if os.environ.get('venv'):
|
||||||
|
venv = os.environ['venv']
|
||||||
|
|
||||||
|
pip_requires = os.path.join(root, 'requirements.txt')
|
||||||
|
test_requires = os.path.join(root, 'test-requirements.txt')
|
||||||
|
py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
|
||||||
|
setup_cfg = ConfigParser.ConfigParser()
|
||||||
|
setup_cfg.read('setup.cfg')
|
||||||
|
project = setup_cfg.get('metadata', 'name')
|
||||||
|
|
||||||
|
install = install_venv.InstallVenv(
|
||||||
|
root, venv, pip_requires, test_requires, py_version, project)
|
||||||
|
options = install.parse_args(argv)
|
||||||
|
install.check_python_version()
|
||||||
|
install.check_dependencies()
|
||||||
|
install.create_virtualenv(no_site_packages=options.no_site_packages)
|
||||||
|
install.install_dependencies()
|
||||||
|
install.post_process()
|
||||||
|
print_help(project, venv, root)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
212
tools/install_venv_common.py
Normal file
212
tools/install_venv_common.py
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
# 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 and pbr
|
||||||
|
self.pip_install('pip>=1.4')
|
||||||
|
self.pip_install('setuptools')
|
||||||
|
self.pip_install('pbr')
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
if os.path.exists('contrib/redhat-eventlet.patch'):
|
||||||
|
# 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')
|
4
tools/with_venv.sh
Executable file
4
tools/with_venv.sh
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
TOOLS=`dirname $0`
|
||||||
|
VENV=$TOOLS/../.venv
|
||||||
|
source $VENV/bin/activate && $@
|
Loading…
x
Reference in New Issue
Block a user