Add bundle import to openstack CLI

usage: openstack bundle import [-h] [-f {csv,json,table,value,yaml}]
                               [-c COLUMN] [--max-width <integer>]
                               [--print-empty] [--noindent]
                               [--quote {all,minimal,none,nonnumeric}]
                               [--is-public] [--exists-action {a,s,u}]
                               [--murano-repo-url MURANO_REPO_URL]
                               <FILE> [<FILE> ...]

Import a bundle.

Partially implements: blueprint openstack-client-plugin-support

Change-Id: I6a5f8c31c1b9671b4ff7f41f83c415261785b635
This commit is contained in:
zhurong 2017-08-18 09:37:59 +08:00
parent 8a83e58e97
commit 6103ec25d9
3 changed files with 258 additions and 0 deletions

View File

@ -515,3 +515,119 @@ class ImportPackage(command.Lister):
columns,
) for s in imported_list)
)
class ImportBundle(command.Lister):
"""Import a bundle."""
def get_parser(self, prog_name):
parser = super(ImportBundle, self).get_parser(prog_name)
parser.add_argument(
'filename',
metavar='<FILE>',
nargs='+',
help='Bundle URL, bundle name, or path to the bundle file.'
)
parser.add_argument(
'--is-public',
action='store_true',
default=False,
help="Make the package available for users from other tenants.",
)
parser.add_argument(
'--exists-action',
default='',
choices=['a', 's', 'u'],
help='Default action when a package already exists: '
'(s)kip, (u)pdate, (a)bort.'
)
parser.add_argument('--murano-repo-url',
default=murano_utils.env(
'MURANO_REPO_URL',
default=DEFAULT_REPO_URL),
help=('Defaults to env[MURANO_REPO_URL] '
'or {0}'.format(DEFAULT_REPO_URL)))
return parser
def take_action(self, parsed_args):
LOG.debug("take_action({0})".format(parsed_args))
client = self.app.client_manager.application_catalog
total_reqs = collections.OrderedDict()
for filename in parsed_args.filename:
local_path = None
if os.path.isfile(filename):
_file = filename
local_path = os.path.dirname(os.path.abspath(filename))
else:
print("Bundle file '{0}' does not exist, attempting "
"to download".format(filename))
_file = murano_utils.to_url(
filename,
base_url=parsed_args.murano_repo_url,
path='bundles/',
extension='.bundle',
)
try:
bundle_file = murano_utils.Bundle.from_file(_file)
except Exception as e:
print("Failed to create bundle for '{0}', reason: {1}".format(
filename, e))
continue
data = {"is_public": parsed_args.is_public}
for package in bundle_file.packages(
base_url=parsed_args.murano_repo_url, path=local_path):
requirements = package.requirements(
base_url=parsed_args.murano_repo_url,
path=local_path,
)
total_reqs.update(requirements)
imported_list = []
for name, dep_package in total_reqs.items():
image_specs = dep_package.images()
if image_specs:
print("Inspecting required images")
try:
imgs = parsed_args.ensure_images(
glance_client=client.glance_client,
image_specs=image_specs,
base_url=parsed_args.murano_repo_url,
local_path=local_path,
is_package_public=parsed_args.is_public)
for img in imgs:
print("Added {0}, {1} image".format(
img['name'], img['id']))
except Exception as e:
print("Error {0} occurred while installing "
"images for {1}".format(e, name))
try:
imported_package = _handle_package_exists(
client, data, dep_package, parsed_args.exists_action)
if imported_package:
imported_list.append(imported_package)
except exceptions.CommandError:
raise
except Exception as e:
print("Error {0} occurred while "
"installing package {1}".format(e, name))
columns = ('id', 'name', 'fully_qualified_name', 'author', 'active',
'is public', 'type', 'version')
column_headers = [c.capitalize() for c in columns]
return (
column_headers,
list(utils.get_item_properties(
s,
columns,
) for s in imported_list)
)

View File

@ -10,7 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import os
import shutil
import six
import sys
import tempfile
@ -548,3 +550,141 @@ class TestPackageImport(TestPackage):
mock.call({'is_public': False}, {'second_app': mock.ANY}),
], any_order=True,
)
class TestBundleImport(TestPackage):
def setUp(self):
super(TestBundleImport, self).setUp()
# Command to test
self.cmd = osc_pkg.ImportBundle(self.app, None)
@requests_mock.mock()
def test_import_bundle_by_name(self, m):
"""Asserts bundle import calls packages create once for each pkg."""
pkg1 = make_pkg({'FullName': 'first_app'})
pkg2 = make_pkg({'FullName': 'second_app'})
murano_repo_url = "http://127.0.0.1"
m.get(murano_repo_url + '/apps/first_app.zip', body=pkg1)
m.get(murano_repo_url + '/apps/second_app.1.0.zip',
body=pkg2)
s = six.StringIO()
bundle_contents = {'Packages': [
{'Name': 'first_app'},
{'Name': 'second_app', 'Version': '1.0'}
]}
json.dump(bundle_contents, s)
s = six.BytesIO(s.getvalue().encode('ascii'))
m.get(murano_repo_url + '/bundles/test_bundle.bundle',
body=s)
arglist = ["test_bundle", '--murano-repo-url', murano_repo_url]
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.package_mock.create.assert_has_calls(
[
mock.call({'is_public': False}, {'first_app': mock.ANY}),
mock.call({'is_public': False}, {'second_app': mock.ANY}),
], any_order=True,
)
@requests_mock.mock()
def test_import_bundle_wrong_url(self, m):
url = 'http://127.0.0.2/test_bundle.bundle'
m.get(url, status_code=404)
arglist = [url]
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.assertFalse(self.package_mock.packages.create.called)
@requests_mock.mock()
def test_import_bundle_no_bundle(self, m):
url = 'http://127.0.0.1/bundles/test_bundle.bundle'
m.get(url, status_code=404)
arglist = ["test_bundle"]
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.assertFalse(self.package_mock.packages.create.called)
@requests_mock.mock()
def test_import_bundle_by_url(self, m):
"""Asserts bundle import calls packages create once for each pkg."""
pkg1 = make_pkg({'FullName': 'first_app'})
pkg2 = make_pkg({'FullName': 'second_app'})
murano_repo_url = 'http://127.0.0.1'
m.get(murano_repo_url + '/apps/first_app.zip', body=pkg1)
m.get(murano_repo_url + '/apps/second_app.1.0.zip',
body=pkg2)
s = six.StringIO()
bundle_contents = {'Packages': [
{'Name': 'first_app'},
{'Name': 'second_app', 'Version': '1.0'}
]}
json.dump(bundle_contents, s)
s = six.BytesIO(s.getvalue().encode('ascii'))
url = 'http://127.0.0.2/test_bundle.bundle'
m.get(url, body=s)
arglist = [url, '--murano-repo-url', murano_repo_url]
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.package_mock.create.assert_has_calls(
[
mock.call({'is_public': False}, {'first_app': mock.ANY}),
mock.call({'is_public': False}, {'second_app': mock.ANY}),
], any_order=True,
)
@requests_mock.mock()
def test_import_local_bundle(self, m):
"""Asserts local bundles are first searched locally."""
tmp_dir = tempfile.mkdtemp()
bundle_file = os.path.join(tmp_dir, 'bundle.bundle')
with open(os.path.join(tmp_dir, 'bundle.bundle'), 'w') as f:
bundle_contents = {'Packages': [
{'Name': 'first_app'},
{'Name': 'second_app', 'Version': '1.0'}
]}
json.dump(bundle_contents, f)
pkg1 = make_pkg({'FullName': 'first_app',
'Require': {'third_app': None}})
pkg2 = make_pkg({'FullName': 'second_app'})
pkg3 = make_pkg({'FullName': 'third_app'})
with open(os.path.join(tmp_dir, 'first_app'), 'wb') as f:
f.write(pkg1.read())
with open(os.path.join(tmp_dir, 'third_app'), 'wb') as f:
f.write(pkg3.read())
murano_repo_url = "http://127.0.0.1"
m.get(murano_repo_url + '/apps/first_app.zip',
status_code=404)
m.get(murano_repo_url + '/apps/second_app.1.0.zip',
body=pkg2)
m.get(murano_repo_url + '/apps/third_app.zip',
status_code=404)
arglist = [bundle_file, '--murano-repo-url', murano_repo_url]
parsed_args = self.check_parser(self.cmd, arglist, [])
self.cmd.take_action(parsed_args)
self.package_mock.create.assert_has_calls(
[
mock.call({'is_public': False}, {'first_app': mock.ANY}),
mock.call({'is_public': False}, {'second_app': mock.ANY}),
mock.call({'is_public': False}, {'third_app': mock.ANY}),
], any_order=True,
)
shutil.rmtree(tmp_dir)

View File

@ -57,6 +57,8 @@ openstack.application_catalog.v1 =
package_delete = muranoclient.osc.v1.package:DeletePackage
package_import = muranoclient.osc.v1.package:ImportPackage
bundle_import = muranoclient.osc.v1.package:ImportBundle
static-action_call = muranoclient.osc.v1.action:StaticActionCall
class-schema = muranoclient.osc.v1.schema:ShowSchema