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:
parent
8a83e58e97
commit
6103ec25d9
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue