Add package create to openstack CLI
usage: openstack package create [-h] [-t <HEAT_TEMPLATE>] [--classes-dir <CLASSES_DIRECTORY>] [-r <RESOURCES_DIRECTORY>] [-n <DISPLAY_NAME>] [--full-name <full-name>] [-a <AUTHOR>] [--tags [<TAG1 TAG2> [<TAG1 TAG2> ...]]] [-d <DESCRIPTION>] [-o <PACKAGE_NAME>] [-u <UI_DEFINITION>] [--type <TYPE>] [-l <LOGO>] Create an application package. Partially implements: blueprint openstack-client-plugin-support Change-Id: I3d81540ede601fe96952dbda483b792924a6fac4
This commit is contained in:
parent
f7bafa1b80
commit
85d360873e
138
muranoclient/osc/v1/package.py
Normal file
138
muranoclient/osc/v1/package.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Application-catalog v1 package action implementation"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
from muranoclient.v1.package_creator import hot_package
|
||||||
|
from muranoclient.v1.package_creator import mpl_package
|
||||||
|
from osc_lib.command import command
|
||||||
|
from osc_lib import exceptions as exc
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePackage(command.Command):
|
||||||
|
"""Create an application package."""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(CreatePackage, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'-t', '--template',
|
||||||
|
metavar='<HEAT_TEMPLATE>',
|
||||||
|
help=("Path to the Heat template to import as "
|
||||||
|
"an Application Definition."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-c', '--classes-dir',
|
||||||
|
metavar='<CLASSES_DIRECTORY>',
|
||||||
|
help=("Path to the directory containing application classes."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-r', '--resources-dir',
|
||||||
|
metavar='<RESOURCES_DIRECTORY>',
|
||||||
|
help=("Path to the directory containing application resources."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-n', '--name',
|
||||||
|
metavar='<DISPLAY_NAME>',
|
||||||
|
help=("Display name of the Application in Catalog."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-f', '--full-name',
|
||||||
|
metavar='<full-name>',
|
||||||
|
help=("Fully-qualified name of the Application in Catalog."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-a', '--author',
|
||||||
|
metavar='<AUTHOR>',
|
||||||
|
help=("Name of the publisher."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--tags',
|
||||||
|
metavar='<TAG1 TAG2>',
|
||||||
|
nargs='*',
|
||||||
|
help=("A list of keywords connected to the application."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-d', '--description',
|
||||||
|
metavar='<DESCRIPTION>',
|
||||||
|
help=("Detailed description for the Application in Catalog."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-o', '--output',
|
||||||
|
metavar='<PACKAGE_NAME>',
|
||||||
|
help=("The name of the output file archive to save locally."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-u', '--ui',
|
||||||
|
metavar='<UI_DEFINITION>',
|
||||||
|
help=("Dynamic UI form definition."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--type',
|
||||||
|
metavar='<TYPE>',
|
||||||
|
help=("Package type. Possible values: Application or Library."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-l', '--logo',
|
||||||
|
metavar='<LOGO>',
|
||||||
|
help=("Path to the package logo."),
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
LOG.debug("take_action({0})".format(parsed_args))
|
||||||
|
parsed_args.os_username = os.getenv('OS_USERNAME')
|
||||||
|
|
||||||
|
def _make_archive(archive_name, path):
|
||||||
|
zip_file = zipfile.ZipFile(archive_name, 'w')
|
||||||
|
for root, dirs, files in os.walk(path):
|
||||||
|
for f in files:
|
||||||
|
zip_file.write(os.path.join(root, f),
|
||||||
|
arcname=os.path.join(
|
||||||
|
os.path.relpath(root, path), f))
|
||||||
|
|
||||||
|
if parsed_args.template and parsed_args.classes_dir:
|
||||||
|
raise exc.CommandError(
|
||||||
|
"Provide --template for a HOT-based package, OR"
|
||||||
|
" --classes-dir for a MuranoPL-based package")
|
||||||
|
if not parsed_args.template and not parsed_args.classes_dir:
|
||||||
|
raise exc.CommandError(
|
||||||
|
"Provide --template for a HOT-based package, OR at least"
|
||||||
|
" --classes-dir for a MuranoPL-based package")
|
||||||
|
directory_path = None
|
||||||
|
try:
|
||||||
|
archive_name = parsed_args.output if parsed_args.output else None
|
||||||
|
if parsed_args.template:
|
||||||
|
directory_path = hot_package.prepare_package(parsed_args)
|
||||||
|
if not archive_name:
|
||||||
|
archive_name = os.path.basename(parsed_args.template)
|
||||||
|
archive_name = os.path.splitext(archive_name)[0] + ".zip"
|
||||||
|
else:
|
||||||
|
directory_path = mpl_package.prepare_package(parsed_args)
|
||||||
|
if not archive_name:
|
||||||
|
archive_name = tempfile.mkstemp(
|
||||||
|
prefix="murano_", dir=os.getcwd())[1] + ".zip"
|
||||||
|
|
||||||
|
_make_archive(archive_name, directory_path)
|
||||||
|
print("Application package is available at " +
|
||||||
|
os.path.abspath(archive_name))
|
||||||
|
finally:
|
||||||
|
if directory_path:
|
||||||
|
shutil.rmtree(directory_path)
|
@ -0,0 +1 @@
|
|||||||
|
heat_template_version: 2013-05-23
|
BIN
muranoclient/tests/unit/osc/v1/fixture_data/logo.png
Normal file
BIN
muranoclient/tests/unit/osc/v1/fixture_data/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -0,0 +1,35 @@
|
|||||||
|
Namespaces:
|
||||||
|
=: io.murano.apps.test
|
||||||
|
std: io.murano
|
||||||
|
res: io.murano.resources
|
||||||
|
|
||||||
|
Name: APP
|
||||||
|
|
||||||
|
Extends: std:Application
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
name:
|
||||||
|
Contract: $.string().notNull()
|
||||||
|
|
||||||
|
instance:
|
||||||
|
Contract: $.class(res:Instance).notNull()
|
||||||
|
|
||||||
|
|
||||||
|
Workflow:
|
||||||
|
initialize:
|
||||||
|
Body:
|
||||||
|
- $.environment: $.find(std:Environment).require()
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
Body:
|
||||||
|
- $securityGroupIngress:
|
||||||
|
- ToPort: 23
|
||||||
|
FromPort: 23
|
||||||
|
IpProtocol: tcp
|
||||||
|
External: True
|
||||||
|
- $.environment.securityGroupManager.addGroupIngress($securityGroupIngress)
|
||||||
|
- $.instance.deploy()
|
||||||
|
- $resources: new('io.murano.system.Resources')
|
||||||
|
- $template: $resources.yaml('Deploy.template')
|
||||||
|
- $.instance.agent.call($template, $resources)
|
||||||
|
|
@ -0,0 +1,21 @@
|
|||||||
|
FormatVersion: 2.0.0
|
||||||
|
Version: 1.0.0
|
||||||
|
Name: Deploy
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
appName: $appName
|
||||||
|
|
||||||
|
Body: |
|
||||||
|
return deploy(args.appName).stdout
|
||||||
|
|
||||||
|
Scripts:
|
||||||
|
deploy:
|
||||||
|
Type: Application
|
||||||
|
Version: 1.0.0
|
||||||
|
EntryPoint: deploy.sh
|
||||||
|
Files:
|
||||||
|
- installer.sh
|
||||||
|
- common.sh
|
||||||
|
Options:
|
||||||
|
captureStdout: true
|
||||||
|
captureStderr: false
|
@ -0,0 +1 @@
|
|||||||
|
#!/bin/bash
|
@ -0,0 +1 @@
|
|||||||
|
#!/bin/bash
|
@ -0,0 +1 @@
|
|||||||
|
#!/bin/bash
|
@ -0,0 +1 @@
|
|||||||
|
Version: 2
|
103
muranoclient/tests/unit/osc/v1/test_package.py
Normal file
103
muranoclient/tests/unit/osc/v1/test_package.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
# 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 six
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from testtools import matchers
|
||||||
|
|
||||||
|
from muranoclient.osc.v1 import package as osc_pkg
|
||||||
|
from muranoclient.tests.unit.osc.v1 import fakes
|
||||||
|
|
||||||
|
from osc_lib import exceptions as exc
|
||||||
|
|
||||||
|
FIXTURE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||||
|
'fixture_data'))
|
||||||
|
|
||||||
|
|
||||||
|
class TestPackage(fakes.TestApplicationCatalog):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestPackage, self).setUp()
|
||||||
|
self.package_mock = self.app.client_manager.application_catalog.\
|
||||||
|
packages
|
||||||
|
self.package_mock.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreatePackage(TestPackage):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestCreatePackage, self).setUp()
|
||||||
|
|
||||||
|
# Command to test
|
||||||
|
self.cmd = osc_pkg.CreatePackage(self.app, None)
|
||||||
|
|
||||||
|
def test_create_package_without_args(self):
|
||||||
|
arglist = []
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||||
|
error = self.assertRaises(exc.CommandError,
|
||||||
|
self.cmd.take_action, parsed_args)
|
||||||
|
self.assertEqual('Provide --template for a HOT-based package, OR at '
|
||||||
|
'least --classes-dir for a MuranoPL-based package',
|
||||||
|
str(error))
|
||||||
|
|
||||||
|
def test_create_package_template_and_classes_args(self):
|
||||||
|
heat_template = os.path.join(FIXTURE_DIR, 'heat-template.yaml')
|
||||||
|
classes_dir = os.path.join(FIXTURE_DIR, 'test-app', 'Classes')
|
||||||
|
arglist = ['--template', heat_template, '--classes-dir', classes_dir]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||||
|
error = self.assertRaises(exc.CommandError,
|
||||||
|
self.cmd.take_action, parsed_args)
|
||||||
|
self.assertEqual('Provide --template for a HOT-based package, OR'
|
||||||
|
' --classes-dir for a MuranoPL-based package',
|
||||||
|
str(error))
|
||||||
|
|
||||||
|
def test_create_hot_based_package(self):
|
||||||
|
with tempfile.NamedTemporaryFile() as f:
|
||||||
|
RESULT_PACKAGE = f.name
|
||||||
|
heat_template = os.path.join(FIXTURE_DIR, 'heat-template.yaml')
|
||||||
|
logo = os.path.join(FIXTURE_DIR, 'logo.png')
|
||||||
|
arglist = ['--template', heat_template, '--output', RESULT_PACKAGE,
|
||||||
|
'-l', logo]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||||
|
orig = sys.stdout
|
||||||
|
try:
|
||||||
|
sys.stdout = six.StringIO()
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
finally:
|
||||||
|
stdout = sys.stdout.getvalue()
|
||||||
|
sys.stdout.close()
|
||||||
|
sys.stdout = orig
|
||||||
|
matchers.MatchesRegex(stdout,
|
||||||
|
"Application package "
|
||||||
|
"is available at {0}".format(RESULT_PACKAGE))
|
||||||
|
|
||||||
|
def test_create_mpl_package(self):
|
||||||
|
with tempfile.NamedTemporaryFile() as f:
|
||||||
|
RESULT_PACKAGE = f.name
|
||||||
|
classes_dir = os.path.join(FIXTURE_DIR, 'test-app', 'Classes')
|
||||||
|
resources_dir = os.path.join(FIXTURE_DIR, 'test-app', 'Resources')
|
||||||
|
ui = os.path.join(FIXTURE_DIR, 'test-app', 'ui.yaml')
|
||||||
|
arglist = ['-c', classes_dir, '-r', resources_dir,
|
||||||
|
'-u', ui, '-o', RESULT_PACKAGE]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||||
|
orig = sys.stdout
|
||||||
|
try:
|
||||||
|
sys.stdout = six.StringIO()
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
finally:
|
||||||
|
stdout = sys.stdout.getvalue()
|
||||||
|
sys.stdout.close()
|
||||||
|
sys.stdout = orig
|
||||||
|
matchers.MatchesRegex(stdout,
|
||||||
|
"Application package "
|
||||||
|
"is available at {0}".format(RESULT_PACKAGE))
|
@ -51,6 +51,8 @@ openstack.application_catalog.v1 =
|
|||||||
|
|
||||||
deployment_list = muranoclient.osc.v1.deployment:ListDeployment
|
deployment_list = muranoclient.osc.v1.deployment:ListDeployment
|
||||||
|
|
||||||
|
package_create = muranoclient.osc.v1.package:CreatePackage
|
||||||
|
|
||||||
static-action_call = muranoclient.osc.v1.action:StaticActionCall
|
static-action_call = muranoclient.osc.v1.action:StaticActionCall
|
||||||
class-schema = muranoclient.osc.v1.schema:ShowSchema
|
class-schema = muranoclient.osc.v1.schema:ShowSchema
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user