Remove djblets package from murano-dashboard.

Use horizon.tables package instead for drawing the tables (old term is
datagrid).  Now columns attribute is configurable in Service
YAML-markup (it should be consistent with initial data for
table). Last thing to be done is to somehow employ Actions from
horizon.tables (if this can be done at all) - this eliminates need in
custom js-code which adds and deletes table rows on client-side.

Change-Id: I6e6cd41b717d48eafd7e4a5e1fc907d2f1ebf12d
Closes-bug: #1234194
This commit is contained in:
Timur Sufiev 2013-09-26 16:09:24 +04:00
parent b3213e54e9
commit 7955c21359
16 changed files with 191 additions and 316 deletions

View File

@ -1,123 +0,0 @@
# Copyright (c) 2013 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 djblets.datagrid.grids import Column, DataGrid
from models import Node, FakeQuerySet
import floppyforms as forms
import json
import re
from django.contrib.auth.models import SiteProfileNotAvailable
class PK(object):
def __init__(self, value=0):
self.value = value
def next(self):
self.value += 1
return self.value
def current(self):
return self.value
class CheckColumn(Column):
def render_data(self, item):
checked = getattr(item, self.field_name)
checked = 'checked="checked"' if checked else ''
return '<input type="checkbox" %s/>' % (checked,)
class RadioColumn(Column):
def __init__(self, *args, **kwargs):
self.name = kwargs.get('name', 'default')
super(RadioColumn, self).__init__(*args, **kwargs)
def render_data(self, item):
checked = getattr(item, self.field_name)
checked = 'checked="checked"' if checked else ''
name = 'name="%s"' % (self.name,)
return '<input type="radio" %s %s/>' % (name, checked)
class NodeDataGrid(DataGrid):
name = Column('Node', sortable=False)
isSync = CheckColumn('Sync')
isMaster = RadioColumn('Primary')
def __init__(self, request, data):
self.pk = PK()
items = []
if type(data) in (str, unicode):
# BEWARE, UGLY HACK!!! Data should be list there already!
data = json.loads(data)
for kwargs in data:
items.append(Node(**dict(kwargs.items() +
[('id', self.pk.next())])))
super(NodeDataGrid, self).__init__(request, FakeQuerySet(
Node, items=items), optimize_sorts=False)
self.default_sort = []
self.default_columns = ['name', 'isSync', 'isMaster']
# hack
def load_state(self, render_context=None):
if self.request.user.is_authenticated():
def get_profile():
raise SiteProfileNotAvailable
setattr(self.request.user, 'get_profile', get_profile)
super(NodeDataGrid, self).load_state(render_context)
class DataGridWidget(forms.widgets.Input):
template_name = 'data_grid_field.html'
def get_context(self, name, value, attrs=None):
ctx = super(DataGridWidget, self).get_context_data()
ctx['data_grid'] = NodeDataGrid(self.request, data=value)
return ctx
def value_from_datadict(self, data, files, name):
base, match = None, re.match('(.*)_[0-9]+', name)
if match:
base = match.group(1)
if base:
pattern = re.compile(base + '_[0-9]+')
for key in data:
if re.match(pattern, key):
return data[key]
return super(DataGridWidget, self).value_from_datadict(
data, files, name)
class Media:
css = {'all': ('css/datagrid.css',
'muranodashboard/css/datagridfield.css')}
js = ('js/jquery.gravy.js',
'js/datagrid.js',
'muranodashboard/js/datagridfield.js')
class DataGridCompound(forms.MultiWidget):
def __init__(self, attrs=None):
_widgets = (DataGridWidget(),
forms.HiddenInput(attrs={'class': 'gridfield-hidden'}))
super(DataGridCompound, self).__init__(_widgets, attrs)
def update_request(self, request):
self.widgets[0].request = request
def decompress(self, value):
if value != '':
return [json.loads(value), value]
else:
return [None, None]

View File

@ -1,77 +1,3 @@
"""
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
"""
from django.db import models
from django.db.models.query import EmptyQuerySet
import copy
class FakeQuerySet(EmptyQuerySet):
"""Turn a list into a Django QuerySet... kind of."""
def __init__(self, model=None, query=None, using=None, items=[]):
super(FakeQuerySet, self).__init__(model, query, using)
self._result_cache = items
def __getitem__(self, k):
if isinstance(k, slice):
obj = self._clone()
obj._result_cache = super(FakeQuerySet, self).__getitem__(k)
return obj
else:
return super(FakeQuerySet, self).__getitem__(k)
def count(self):
return len(self)
def _clone(self, klass=None, setup=False, **kwargs):
c = super(FakeQuerySet, self)._clone(klass, setup=setup, **kwargs)
c._result_cache = copy.copy(self._result_cache)
return c
def iterator(self):
# This slightly odd construction is because we need an empty generator
# (it raises StopIteration immediately).
yield iter(self._result_cache).next()
def order_by(self, *fields):
obj = self._clone()
cache = obj._result_cache
for field in fields:
reverse = False
if field[0] == '-':
reverse = True
field = field[1:]
cache = sorted(cache, None, lambda item: getattr(item, field),
reverse=reverse)
obj._result_cache = cache
return obj
def distinct(self, *fields):
obj = self._clone()
return obj
def values_list(self, *fields, **kwargs):
obj = self._clone()
cache = []
for item in self._result_cache:
value = []
for field in fields:
value.append(getattr(item, field))
cache.append(tuple(value))
if kwargs.get('flat', False) and len(fields) == 1:
cache = [item[0] for item in cache]
obj._result_cache = cache
return obj
def add(self, item):
self._result_cache.append(item)
def remove(self):
self._result_cache.pop()
class Node(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=20)
isMaster = models.BooleanField(default=False)
isSync = models.BooleanField(default=False)

View File

@ -23,11 +23,14 @@ from muranodashboard.panel import api
from horizon import exceptions, messages
from openstack_dashboard.api import glance
from openstack_dashboard.api.nova import novaclient
from muranodashboard.datagrids import DataGridCompound
from django.template.defaultfilters import pluralize
import copy
import types
import logging
import itertools
import horizon.tables as tables
import floppyforms
from django.template.loader import render_to_string
log = logging.getLogger(__name__)
@ -168,24 +171,127 @@ class InstanceCountField(IntegerField):
return interpolate(spec)
class DataGridField(forms.MultiValueField, CustomPropertiesField):
def __init__(self, *args, **kwargs):
kwargs['widget'] = DataGridCompound
super(DataGridField, self).__init__(
(forms.CharField(required=False), forms.CharField()),
*args, **kwargs)
class Column(tables.Column):
template_name = 'common/form-fields/data-grid/input.html'
def compress(self, data_list):
return data_list[1]
def __init__(self, transform, **kwargs):
table_name = kwargs.pop('table_name', False)
if hasattr(self, 'template_name'):
def _transform(datum):
context = {'data': getattr(datum, self.name, None),
'row_index': str(datum.id),
'table_name': table_name,
'column_name': self.name}
return render_to_string(self.template_name, context)
_transform.__name__ = transform
transform = _transform
super(Column, self).__init__(transform, **kwargs)
class CheckColumn(Column):
template_name = 'common/form-fields/data-grid/checkbox.html'
class RadioColumn(Column):
template_name = 'common/form-fields/data-grid/radio.html'
def DataTableFactory(name, columns):
class Object(object):
row_name_re = re.compile(r'.*\{0}.*')
def __init__(self, id, **kwargs):
self.id = id
for key, value in kwargs.iteritems():
if isinstance(value, basestring) and \
re.match(self.row_name_re, value):
setattr(self, key, value.format(id))
else:
setattr(self, key, value)
class DataTableBase(tables.DataTable):
def __init__(self, request, data, **kwargs):
super(DataTableBase, self).__init__(
request,
[Object(i, **item) for (i, item) in enumerate(data, 1)],
**kwargs)
class Meta:
template = 'common/form-fields/data-grid/data_table.html'
name = ''
footer = False
attrs = dict((col_id, cls(col_id, verbose_name=col_name, table_name=name))
for (col_id, cls, col_name) in columns)
attrs['Meta'] = Meta
return tables.base.DataTableMetaclass('DataTable', (DataTableBase,), attrs)
class TableWidget(floppyforms.widgets.Input):
template_name = 'common/form-fields/data-grid/table_field.html'
delimiter_re = re.compile('([\w-]*)@@([0-9]*)@@([\w-]*)')
types = {'label': Column,
'radio': RadioColumn,
'checkbox': CheckColumn}
def __init__(self, columns_spec, *args, **kwargs):
columns = []
for spec in columns_spec:
name = spec['column_name']
columns.append((name,
self.types[spec['column_type']],
spec.get('title', None) or name.title()))
self.columns = columns
super(TableWidget, self).__init__(*args, **kwargs)
def get_context(self, name, value, attrs=None):
ctx = super(TableWidget, self).get_context_data()
if value:
ctx['data_table'] = DataTableFactory(name, self.columns)(
self.request, value)
return ctx
def value_from_datadict(self, data, files, name):
def extract_value(row_idx, col_id, col_cls):
if col_cls == CheckColumn:
val = data.get("{0}@@{1}@@{2}".format(name, row_idx, col_id),
False)
return val and val == 'on'
elif col_cls == RadioColumn:
row_id = data.get("{0}@@@@{1}".format(name, col_id), False)
if row_id:
return int(row_id) == row_idx
return False
else:
return data.get("{0}@@{1}@@{2}".format(
name, row_idx, col_id), None)
items = []
main_column, rest_columns = self.columns[0], self.columns[1:]
for row_index in itertools.count(1):
if not extract_value(row_index, *main_column[:2]):
break
item = {}
for column_id, column_instance, column_name in self.columns:
value = extract_value(row_index, column_id, column_instance)
item[column_id] = value
items.append(item)
print items
return items
class Media:
css = {'all': ('muranodashboard/css/tablefield.css',)}
js = ('muranodashboard/js/tablefield.js',)
class TableField(CustomPropertiesField):
def __init__(self, *args, **kwargs):
kwargs['widget'] = TableWidget(kwargs.pop('columns'))
super(TableField, self).__init__(*args, **kwargs)
@with_request
def update(self, request, **kwargs):
self.widget.update_request(request)
# hack to use json string instead of python dict get by YAQL
data = kwargs['form'].service.cleaned_data
if 'clusterConfiguration' in data:
conf = data['clusterConfiguration']
conf['dcInstances'] = json.dumps(conf['dcInstances'])
self.widget.request = request
class ChoiceField(forms.ChoiceField, CustomPropertiesField):

View File

@ -27,8 +27,7 @@ log = logging.getLogger(__name__)
class UpdatableFieldsForm(forms.Form):
def update_fields(self):
#Create 'Confirm Password' fields by duplicating" Password fields
# Create 'Confirm Password' fields by duplicating password fields
while True:
index, inserted = 0, False
for name, field in self.fields.iteritems():
@ -60,7 +59,7 @@ class ServiceConfigurationForm(UpdatableFieldsForm):
'password': fields.PasswordField,
'integer': fields.IntegerField,
'databaselist': fields.DatabaseListField,
'datagrid': fields.DataGridField,
'table': fields.TableField,
'flavor': fields.FlavorChoiceField,
'image': fields.ImageChoiceField,
'azone': fields.AZoneChoiceField,
@ -74,6 +73,7 @@ class ServiceConfigurationForm(UpdatableFieldsForm):
super(ServiceConfigurationForm, self).__init__(*args, **kwargs)
self.attribute_mappings = {}
self.insert_fields(self.fields_template)
self.initial = kwargs.get('initial', self.initial)
self.update_fields()
@staticmethod
@ -203,6 +203,7 @@ class ServiceConfigurationForm(UpdatableFieldsForm):
return 'validators', [prepare_regexp(spec)]
else:
return key, spec
for spec in field_specs:
append_field(spec)

View File

@ -60,11 +60,6 @@ class Wizard(ModalFormMixin, SessionWizardView):
for form in form_list[1:]:
form.extract_attributes(attributes)
# hack to destringify nodes into units
if 'nodes' in attributes:
attributes['units'] = json.loads(attributes['nodes'])
del attributes['nodes']
try:
api.service_create(self.request, environment_id, attributes)
except HTTPForbidden:

View File

@ -8,13 +8,13 @@ description: >-
unitTemplates:
- isMaster: true
isSync: true
name: 'node#'
name: 'node-#'
- isMaster: false
isSync: true
name: 'node#'
name: 'node-#'
- isMaster: false
isSync: false
name: 'node#'
name: 'node-#'
forms:
- serviceConfiguration:
@ -169,8 +169,19 @@ forms:
- unitsConfiguration:
fields:
- name: nodes
type: datagrid
type: table
label: Nodes
attributeNames: units
columns:
- columnName: name
columnType: label
title: Node
- columnName: isSync
columnType: checkbox
title: Sync
- columnName: isMaster
columnType: radio
title: Master
initial: {YAQL: $.clusterConfiguration.dcInstances}
description: >-
Configure cluster instances. Cluster node quantity can be set

View File

@ -1,3 +0,0 @@
.left {
overflow: hidden;
}

View File

@ -0,0 +1,10 @@
input[type=radio].data-grid, input[type=checkbox].data-grid {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
width: auto !important;
}
.data-grid table {
margin-bottom: 5px ! important;;
}

View File

@ -24,11 +24,11 @@ $(function() {
var labels = [],
max = 0,
base = '';
$('table.datagrid tbody tr td:first-child').each(function(i, td) {
$('.data-grid table tbody tr td:first-child').each(function(i, td) {
labels.push(trimLabel($(td).text()));
});
labels.forEach(function(label) {
var match = /([a-zA-z]+)([0-9]+)/.exec(label),
var match = /([a-zA-z]+)-([0-9]+)/.exec(label),
n = +match[2];
base = match[1];
if ( n > max )
@ -39,12 +39,12 @@ $(function() {
function getNextLabel() {
var baseMax = getMaxLabel();
return baseMax[0]+(+baseMax[1]+1);
return baseMax[0]+'-'+(+baseMax[1]+1);
}
function getNumOfSync() {
var checked = 0;
$('table.datagrid tbody td input:checkbox').each(function(index, cb) {
$('.data-grid table tbody td input:checkbox').each(function(index, cb) {
if ( $(cb).attr('checked') )
checked++;
});
@ -65,7 +65,7 @@ $(function() {
}
}
var primary = $('table.datagrid tbody input:radio[checked="checked"]').parents().eq(1);
var primary = $('.data-grid table tbody input:radio[checked="checked"]').parents().eq(1);
function validate_primary(event) {
var radio = $(event.target),
@ -78,19 +78,25 @@ $(function() {
primary = radio.parents().eq(1);
}
$('table.datagrid tbody td input:checkbox').click(validate_sync);
$('table.datagrid tbody td input:radio').click(validate_primary);
$('.data-grid table tbody td input:checkbox').click(validate_sync);
$('.data-grid table tbody td input:radio').click(validate_primary);
$('button#node-add').click(function() {
//debugger;
if ( $('table.datagrid tbody tr').length >= MAX_NODES ) {
if ( $('.data-grid table tbody tr').length >= MAX_NODES ) {
alert('Maximum number of nodes ('+MAX_NODES+') already reached.');
return;
}
var lastRow = $('table.datagrid tbody tr:last-child'),
clone = lastRow.clone();
clone.toggleClass('even').toggleClass('odd');
clone.find('td:first-child').text(getNextLabel());
var lastRow = $('.data-grid table tbody tr:last-child'),
clone = lastRow.clone(),
nameCell = clone.find('td:first-child'),
hidden = nameCell.find('input:hidden'),
match = /([^@]+)@@([0-9]+)@@(.+)/.exec(hidden.attr('name')),
index = +match[2]+1,
nextLabel = getNextLabel();
nameCell.text(nextLabel);
hidden.val(nextLabel);
hidden.attr('name', match[1]+'@@'+index+'@@'+match[3]);
nameCell.append(hidden);
// toggle of sync and primary buttons of clone
clone.find('input:checkbox').attr('checked', false);
clone.find('input:checkbox').click(validate_sync);
@ -100,37 +106,18 @@ $(function() {
});
$('button#node-remove').click(function() {
if ( $('table.datagrid tbody tr').length <= MIN_NODES ) {
if ( $('.data-grid table tbody tr').length <= MIN_NODES ) {
alert('There cannot be less than ' + MIN_NODES + ' nodes');
return;
}
var labelNum = getMaxLabel(),
label = labelNum.join(''),
rowRef = 'table.datagrid tbody tr:contains('+label+')';
if ( rowRef + ' :radio[checked="checked"]' ) {
label = labelNum[0] + (labelNum[1] - 1);
$('table.datagrid tbody tr:contains('+label+') :radio').attr(
label = labelNum.join('-'),
rowRef = '.data-grid table tbody tr:contains('+label+')';
if ( $(rowRef + ' :radio[checked="checked"]') ) {
label = labelNum[0] + '-' + (labelNum[1] - 1);
$('.data-grid table tbody tr:contains('+label+') :radio').attr(
'checked', 'checked');
}
$(rowRef).remove();
});
$('.modal-footer input.btn-primary').click(function() {
var data = [];
$('table.datagrid tbody tr').each(function(i, tr) {
function getInputVal(td) {
return td.find('input').attr('checked') == 'checked';
}
data.push({
name: trimLabel($(tr).children().eq(0).text()),
isSync: getInputVal($(tr).children().eq(1)),
isMaster: getInputVal($(tr).children().eq(2))
})
});
$('input.gridfield-hidden').val(JSON.stringify(data));
})
// temporarily disable all controls which are bugged
$('table.datagrid th.edit-columns').remove();
$('div.datagrid-titlebox').remove();
});

View File

@ -0,0 +1,3 @@
<input type="checkbox" class="data-grid"
{% if data %}checked="checked"{% endif %}
name="{{ table_name }}@@{{ row_index }}@@{{ column_name }}"/>

View File

@ -0,0 +1,2 @@
{% extends 'horizon/common/_data_table.html' %}
{% block table_caption %}{% endblock table_caption %}

View File

@ -0,0 +1,4 @@
{{ data }}
<input type="hidden" class="data-grid"
name="{{ table_name }}@@{{ row_index }}@@{{ column_name }}"
value="{{ data }}"/>

View File

@ -0,0 +1,4 @@
<input type="radio" class="data-grid"
{% if data %}checked="checked"{% endif %}
name="{{ table_name }}@@@@{{ column_name }}"
value="{{ row_index }}"/>

View File

@ -1,3 +1,3 @@
{{ data_grid.render_listview }}
<div class="data-grid">{{ data_table.render }}</div>
<button type="button" id="node-add" class="btn btn-small">Add node</button>
<button type="button" id="node-remove" class="btn btn-small">Remove node</button>

View File

@ -23,7 +23,6 @@ PIPCMD=""
SERVICE_SRV_NAME="murano-dashboard"
GIT_CLONE_DIR=`echo $SERVICE_CONTENT_DIRECTORY | sed -e "s/$SERVICE_SRV_NAME//"`
HORIZON_CONFIGS="/opt/stack/horizon/openstack_dashboard/settings.py,/usr/share/openstack-dashboard/openstack_dashboard/settings.py"
NON_PIP_PACKAGES_BASE_URL=https://github.com
# Functions
# Loger function
log()
@ -117,7 +116,7 @@ HORIZON_CONFIG['exceptions']['recoverable'] = EXTENDED_RECOVERABLE_EXCEPTIONS
HORIZON_CONFIG['exceptions']['not_found'] = EXTENDED_NOT_FOUND_EXCEPTIONS
HORIZON_CONFIG['exceptions']['unauthorized'] = EXTENDED_UNAUTHORIZED_EXCEPTIONS
HORIZON_CONFIG['customization_module'] = 'muranodashboard.panel.overrides'
INSTALLED_APPS += ('muranodashboard','djblets','djblets.datagrid','djblets.util','floppyforms',)
INSTALLED_APPS += ('muranodashboard','floppyforms',)
MIDDLEWARE_CLASSES += ('muranodashboard.settings.ExceptionMiddleware',)
LOGGING['formatters'] = {'verbose': {'format': '[%(asctime)s] [%(levelname)s] [pid=%(process)d] %(message)s'}}
LOGGING['handlers']['file'] = {'level': 'DEBUG', 'formatter': 'verbose', 'class': 'logging.FileHandler', 'filename': '/var/log/murano-dashboard.log'}
@ -256,29 +255,6 @@ CLONE_FROM_GIT=$1
log "$PIPCMD install \"$TRBL_FILE\" FAILS, exiting!!!"
exit 1
fi
# NON PIP PACKAGES INSTALL START
for pkg in tsufiev.djblets; do
PACKAGE=${pkg##*.}
OWNER=${pkg%.*}
SUFFIX=master.zip
PACKAGE_OUTARCH_FILENAME=$PACKAGE-$SUFFIX
cd $SERVICE_CONTENT_DIRECTORY/dist && wget $NON_PIP_PACKAGES_BASE_URL/$OWNER/$PACKAGE/archive/$SUFFIX -O $PACKAGE_OUTARCH_FILENAME
if [ $? -ne 0 ];then
log " Can't download \"$PACKAGE_OUTARCH_FILENAME\", exiting!!!"
exit 1
fi
cd $SERVICE_CONTENT_DIRECTORY/dist && unzip $PACKAGE_OUTARCH_FILENAME
if [ $? -ne 0 ];then
log " Can't unzip \"$SERVICE_CONTENT_DIRECTORY/dist/$PACKAGE_OUTARCH_FILENAME\", exiting!!!"
exit 1
fi
cd $SERVICE_CONTENT_DIRECTORY/dist/$PACKAGE-${SUFFIX%.*} && python setup.py install
if [ $? -ne 0 ]; then
log "\"$SERVICE_CONTENT_DIRECTORY/dist/$PACKAGE-${SUFFIX%.*}/setup.py\" python setup FAILS, exiting!"
exit 1
fi
done
# NON PIP PACKAGES INSTALL END
else
log "$MRN_CND_SPY not found!"
fi

View File

@ -21,7 +21,6 @@ PREREQ_PKGS="wget make git python-pip python-dev python-mysqldb libxml2-dev libx
SERVICE_SRV_NAME="murano-dashboard"
GIT_CLONE_DIR=`echo $SERVICE_CONTENT_DIRECTORY | sed -e "s/$SERVICE_SRV_NAME//"`
HORIZON_CONFIGS="/opt/stack/horizon/openstack_dashboard/settings.py,/usr/share/openstack-dashboard/openstack_dashboard/settings.py"
NON_PIP_PACKAGES_BASE_URL=https://github.com
# Functions
# Logger function
@ -98,7 +97,7 @@ HORIZON_CONFIG['exceptions']['recoverable'] = EXTENDED_RECOVERABLE_EXCEPTIONS
HORIZON_CONFIG['exceptions']['not_found'] = EXTENDED_NOT_FOUND_EXCEPTIONS
HORIZON_CONFIG['exceptions']['unauthorized'] = EXTENDED_UNAUTHORIZED_EXCEPTIONS
HORIZON_CONFIG['customization_module'] = 'muranodashboard.panel.overrides'
INSTALLED_APPS += ('muranodashboard','djblets','djblets.datagrid','djblets.util','floppyforms',)
INSTALLED_APPS += ('muranodashboard','floppyforms',)
MIDDLEWARE_CLASSES += ('muranodashboard.settings.ExceptionMiddleware',)
LOGGING['formatters'] = {'verbose': {'format': '[%(asctime)s] [%(levelname)s] [pid=%(process)d] %(message)s'}}
LOGGING['handlers']['file'] = {'level': 'DEBUG', 'formatter': 'verbose', 'class': 'logging.FileHandler', 'filename': '/var/log/murano-dashboard.log'}
@ -192,29 +191,6 @@ CLONE_FROM_GIT=$1
log "pip install \"$TRBL_FILE\" FAILS, exiting!!!"
exit 1
fi
# NON PIP PACKAGES INSTALL START
for pkg in tsufiev.djblets; do
PACKAGE=${pkg##*.}
OWNER=${pkg%.*}
SUFFIX=master.zip
PACKAGE_OUTARCH_FILENAME=$PACKAGE-$SUFFIX
cd $SERVICE_CONTENT_DIRECTORY/dist && wget $NON_PIP_PACKAGES_BASE_URL/$OWNER/$PACKAGE/archive/$SUFFIX -O $PACKAGE_OUTARCH_FILENAME
if [ $? -ne 0 ];then
log " Can't download \"$PACKAGE_OUTARCH_FILENAME\", exiting!!!"
exit 1
fi
cd $SERVICE_CONTENT_DIRECTORY/dist && unzip $PACKAGE_OUTARCH_FILENAME
if [ $? -ne 0 ];then
log " Can't unzip \"$SERVICE_CONTENT_DIRECTORY/dist/$PACKAGE_OUTARCH_FILENAME\", exiting!!!"
exit 1
fi
cd $SERVICE_CONTENT_DIRECTORY/dist/$PACKAGE-${SUFFIX%.*} && python setup.py install
if [ $? -ne 0 ]; then
log "\"$SERVICE_CONTENT_DIRECTORY/dist/$PACKAGE-${SUFFIX%.*}/setup.py\" python setup FAILS, exiting!"
exit 1
fi
done
# NON PIP PACKAGES INSTALL END
else
log "$MRN_CND_SPY not found!"
fi