Add package-create command
This command composes application package from heat templates and for muranoPL classes. Manifest file is generated automatically. For a list values, such as tags and categories comma is used. Partly-implements: blueprint murano-cli-client Change-Id: I6005bf24761e5537681156fe4fa17bcc11bc7501
This commit is contained in:
parent
1a7db5f125
commit
d62b0f2d6e
6
.gitignore
vendored
6
.gitignore
vendored
@ -24,3 +24,9 @@ ChangeLog
|
|||||||
|
|
||||||
#Autogenerated Documentation
|
#Autogenerated Documentation
|
||||||
doc/source/api
|
doc/source/api
|
||||||
|
|
||||||
|
#Testing framework
|
||||||
|
.testrepository
|
||||||
|
.coverage
|
||||||
|
*,cover
|
||||||
|
cover
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
# Copyright (c) 2014 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
_ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def get_resource(path):
|
||||||
|
return os.path.join(_ROOT, 'data', path)
|
@ -16,12 +16,17 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
import types
|
||||||
import uuid
|
import uuid
|
||||||
|
import yaml
|
||||||
|
|
||||||
import prettytable
|
import prettytable
|
||||||
import six
|
import six
|
||||||
|
import yaql
|
||||||
|
import yaql.exceptions
|
||||||
|
|
||||||
from muranoclient.common import exceptions
|
from muranoclient.common import exceptions
|
||||||
from muranoclient.openstack.common import importutils
|
from muranoclient.openstack.common import importutils
|
||||||
@ -156,3 +161,53 @@ def exception_to_str(exc):
|
|||||||
error = ("Caught '%(exception)s' exception." %
|
error = ("Caught '%(exception)s' exception." %
|
||||||
{"exception": exc.__class__.__name__})
|
{"exception": exc.__class__.__name__})
|
||||||
return strutils.safe_encode(error, errors='ignore')
|
return strutils.safe_encode(error, errors='ignore')
|
||||||
|
|
||||||
|
|
||||||
|
class YaqlExpression(object):
|
||||||
|
def __init__(self, expression):
|
||||||
|
self._expression = str(expression)
|
||||||
|
self._parsed_expression = yaql.parse(self._expression)
|
||||||
|
|
||||||
|
def expression(self):
|
||||||
|
return self._expression
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'YAQL(%s)' % self._expression
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self._expression
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def match(expr):
|
||||||
|
if not isinstance(expr, types.StringTypes):
|
||||||
|
return False
|
||||||
|
if re.match('^[\s\w\d.:]*$', expr):
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
yaql.parse(expr)
|
||||||
|
return True
|
||||||
|
except yaql.exceptions.YaqlGrammarException:
|
||||||
|
return False
|
||||||
|
except yaql.exceptions.YaqlLexicalException:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def evaluate(self, data=None, context=None):
|
||||||
|
return self._parsed_expression.evaluate(data=data, context=context)
|
||||||
|
|
||||||
|
|
||||||
|
class YaqlYamlLoader(yaml.Loader):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# workaround for PyYAML bug: http://pyyaml.org/ticket/221
|
||||||
|
resolvers = {}
|
||||||
|
for k, v in yaml.Loader.yaml_implicit_resolvers.items():
|
||||||
|
resolvers[k] = v[:]
|
||||||
|
YaqlYamlLoader.yaml_implicit_resolvers = resolvers
|
||||||
|
|
||||||
|
|
||||||
|
def yaql_constructor(loader, node):
|
||||||
|
value = loader.construct_scalar(node)
|
||||||
|
return YaqlExpression(value)
|
||||||
|
|
||||||
|
yaml.add_constructor(u'!yaql', yaql_constructor, YaqlYamlLoader)
|
||||||
|
yaml.add_implicit_resolver(u'!yaql', YaqlExpression, Loader=YaqlYamlLoader)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# Copyright 2010 Jacob Kaplan-Moss
|
|
||||||
# Copyright 2011 Nebula, Inc.
|
# Copyright 2011 Nebula, Inc.
|
||||||
# Copyright 2013 Alessio Ababilov
|
# Copyright 2013 Alessio Ababilov
|
||||||
# Copyright 2013 OpenStack Foundation
|
# Copyright 2013 OpenStack Foundation
|
||||||
|
@ -1,13 +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.
|
|
45
muranoclient/tests/base.py
Normal file
45
muranoclient/tests/base.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Copyright 2011 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 fixtures
|
||||||
|
import six
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseShell(testtools.TestCase):
|
||||||
|
TEST_REQUEST_BASE = {
|
||||||
|
'verify': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestCaseShell, self).setUp()
|
||||||
|
if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
|
||||||
|
os.environ.get('OS_STDOUT_CAPTURE') == '1'):
|
||||||
|
stdout = self.useFixture(fixtures.StringStream('stdout')).stream
|
||||||
|
self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
|
||||||
|
if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
|
||||||
|
os.environ.get('OS_STDERR_CAPTURE') == '1'):
|
||||||
|
stderr = self.useFixture(fixtures.StringStream('stderr')).stream
|
||||||
|
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
|
||||||
|
|
||||||
|
|
||||||
|
class TestAdditionalAsserts(testtools.TestCase):
|
||||||
|
|
||||||
|
def check_dict_is_subset(self, dict1, dict2):
|
||||||
|
# There is an assert for this in Python 2.7 but not 2.6
|
||||||
|
self.assertTrue(all(k in dict2 and dict2[k] == v for k, v
|
||||||
|
in six.iteritems(dict1)))
|
1
muranoclient/tests/fixture_data/heat-template.yaml
Normal file
1
muranoclient/tests/fixture_data/heat-template.yaml
Normal file
@ -0,0 +1 @@
|
|||||||
|
heat_template_version: 2013-05-23
|
BIN
muranoclient/tests/fixture_data/logo.png
Normal file
BIN
muranoclient/tests/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
|
1
muranoclient/tests/fixture_data/test-app/ui.yaml
Normal file
1
muranoclient/tests/fixture_data/test-app/ui.yaml
Normal file
@ -0,0 +1 @@
|
|||||||
|
Version: 2
|
198
muranoclient/tests/test_package_creator.py
Normal file
198
muranoclient/tests/test_package_creator.py
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
# Copyright (c) 2014 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from muranoclient.openstack.common.apiclient import exceptions
|
||||||
|
from muranoclient.tests import base
|
||||||
|
from muranoclient.v1.package_creator import hot_package
|
||||||
|
from muranoclient.v1.package_creator import mpl_package
|
||||||
|
|
||||||
|
|
||||||
|
FIXTURE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||||
|
'fixture_data'))
|
||||||
|
TEMPLATE = os.path.join(FIXTURE_DIR, 'heat-template.yaml')
|
||||||
|
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')
|
||||||
|
LOGO = os.path.join(FIXTURE_DIR, 'logo.png')
|
||||||
|
|
||||||
|
|
||||||
|
class TestArgs(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PackageCreatorTest(base.TestAdditionalAsserts):
|
||||||
|
|
||||||
|
def test_generate_hot_manifest(self):
|
||||||
|
args = TestArgs()
|
||||||
|
args.template = TEMPLATE
|
||||||
|
args.name = 'test_name'
|
||||||
|
args.author = 'TestAuthor'
|
||||||
|
args.full_name = None
|
||||||
|
args.tags = None
|
||||||
|
args.description = None
|
||||||
|
expected_manifest = {
|
||||||
|
'Format': 'Heat.HOT/1.0',
|
||||||
|
'Type': 'Application',
|
||||||
|
'FullName': 'io.murano.apps.generated.TestName',
|
||||||
|
'Name': 'test_name',
|
||||||
|
'Description': 'Heat-defined application '
|
||||||
|
'for a template "heat-template.yaml"',
|
||||||
|
'Author': 'TestAuthor',
|
||||||
|
'Tags': ['Heat-generated']
|
||||||
|
}
|
||||||
|
result_manifest = hot_package.generate_manifest(args)
|
||||||
|
self.check_dict_is_subset(expected_manifest, result_manifest)
|
||||||
|
|
||||||
|
def test_generate_hot_manifest_nonexistent_template(self):
|
||||||
|
args = TestArgs()
|
||||||
|
args.template = '/home/this/path/does/not/exist'
|
||||||
|
self.assertRaises(exceptions.CommandError,
|
||||||
|
hot_package.generate_manifest,
|
||||||
|
args)
|
||||||
|
|
||||||
|
def test_generate_hot_manifest_with_all_parameters(self):
|
||||||
|
args = TestArgs()
|
||||||
|
args.template = TEMPLATE
|
||||||
|
args.name = 'test_name'
|
||||||
|
args.author = 'TestAuthor'
|
||||||
|
args.full_name = 'test.full.name.TestName'
|
||||||
|
args.tags = ['test', 'tag', 'Heat']
|
||||||
|
args.description = 'Test description'
|
||||||
|
|
||||||
|
expected_manifest = {
|
||||||
|
'Format': 'Heat.HOT/1.0',
|
||||||
|
'Type': 'Application',
|
||||||
|
'FullName': 'test.full.name.TestName',
|
||||||
|
'Name': 'test_name',
|
||||||
|
'Description': 'Test description',
|
||||||
|
'Author': 'TestAuthor',
|
||||||
|
'Tags': ['test', 'tag', 'Heat']
|
||||||
|
}
|
||||||
|
result_manifest = hot_package.generate_manifest(args)
|
||||||
|
self.check_dict_is_subset(expected_manifest, result_manifest)
|
||||||
|
|
||||||
|
def test_generate_hot_manifest_template_not_yaml(self):
|
||||||
|
args = TestArgs()
|
||||||
|
args.template = LOGO
|
||||||
|
args.name = None
|
||||||
|
args.full_name = None
|
||||||
|
self.assertRaises(exceptions.CommandError,
|
||||||
|
hot_package.generate_manifest, args)
|
||||||
|
|
||||||
|
def test_prepare_hot_package(self):
|
||||||
|
args = TestArgs()
|
||||||
|
args.template = TEMPLATE
|
||||||
|
args.name = 'test_name'
|
||||||
|
args.author = 'TestAuthor'
|
||||||
|
args.full_name = 'test.full.name.TestName'
|
||||||
|
args.tags = 'test, tag, Heat'
|
||||||
|
args.description = 'Test description'
|
||||||
|
args.logo = LOGO
|
||||||
|
package_dir = hot_package.prepare_package(args)
|
||||||
|
|
||||||
|
prepared_files = ['manifest.yaml', 'logo.png', 'template.yaml']
|
||||||
|
self.assertEqual(sorted(os.listdir(package_dir)),
|
||||||
|
sorted(prepared_files))
|
||||||
|
shutil.rmtree(package_dir)
|
||||||
|
|
||||||
|
def test_generate_mpl_manifest(self):
|
||||||
|
args = TestArgs()
|
||||||
|
args.template = TEMPLATE
|
||||||
|
args.classes_dir = CLASSES_DIR
|
||||||
|
args.resources_dir = RESOURCES_DIR
|
||||||
|
args.type = 'Application'
|
||||||
|
args.author = 'TestAuthor'
|
||||||
|
args.name = None
|
||||||
|
args.full_name = None
|
||||||
|
args.tags = None
|
||||||
|
args.description = None
|
||||||
|
|
||||||
|
expected_manifest = {
|
||||||
|
'Format': 'MuranoPL/1.0',
|
||||||
|
'Type': 'Application',
|
||||||
|
'Classes': {'io.murano.apps.test.APP': 'testapp.yaml'},
|
||||||
|
'FullName': 'io.murano.apps.test.APP',
|
||||||
|
'Name': 'APP',
|
||||||
|
'Description': 'Description for the application is not provided',
|
||||||
|
'Author': 'TestAuthor',
|
||||||
|
}
|
||||||
|
result_manifest = mpl_package.generate_manifest(args)
|
||||||
|
self.check_dict_is_subset(expected_manifest, result_manifest)
|
||||||
|
|
||||||
|
def test_generate_mpl_manifest_with_all_parameters(self):
|
||||||
|
args = TestArgs()
|
||||||
|
args.template = TEMPLATE
|
||||||
|
args.classes_dir = CLASSES_DIR
|
||||||
|
args.resources_dir = RESOURCES_DIR
|
||||||
|
args.type = 'Application'
|
||||||
|
args.name = 'test_name'
|
||||||
|
args.author = 'TestAuthor'
|
||||||
|
args.full_name = 'test.full.name.TestName'
|
||||||
|
args.tags = ['test', 'tag', 'Heat']
|
||||||
|
args.description = 'Test description'
|
||||||
|
|
||||||
|
expected_manifest = {
|
||||||
|
'Format': 'MuranoPL/1.0',
|
||||||
|
'Type': 'Application',
|
||||||
|
'Classes': {'io.murano.apps.test.APP': 'testapp.yaml'},
|
||||||
|
'FullName': 'test.full.name.TestName',
|
||||||
|
'Name': 'test_name',
|
||||||
|
'Description': 'Test description',
|
||||||
|
'Author': 'TestAuthor',
|
||||||
|
'Tags': ['test', 'tag', 'Heat']
|
||||||
|
}
|
||||||
|
result_manifest = mpl_package.generate_manifest(args)
|
||||||
|
self.check_dict_is_subset(expected_manifest, result_manifest)
|
||||||
|
|
||||||
|
def test_generate_mpl_wrong_classes_dir(self):
|
||||||
|
args = TestArgs()
|
||||||
|
args.classes_dir = '/home/this/path/does/not/exist'
|
||||||
|
expected = ("'--classes-dir' parameter should be a directory", )
|
||||||
|
try:
|
||||||
|
mpl_package.generate_manifest(args)
|
||||||
|
except exceptions.CommandError as message:
|
||||||
|
self.assertEqual(expected, message.args)
|
||||||
|
|
||||||
|
def test_generate_mpl_wrong_resources_dir(self):
|
||||||
|
args = TestArgs()
|
||||||
|
args.classes_dir = CLASSES_DIR
|
||||||
|
args.resources_dir = '/home/this/path/does/not/exist'
|
||||||
|
expected = ("'--resources-dir' parameter should be a directory", )
|
||||||
|
try:
|
||||||
|
mpl_package.generate_manifest(args)
|
||||||
|
except exceptions.CommandError as message:
|
||||||
|
self.assertEqual(expected, message.args)
|
||||||
|
|
||||||
|
def test_prepare_mpl_package(self):
|
||||||
|
args = TestArgs()
|
||||||
|
args.template = TEMPLATE
|
||||||
|
args.classes_dir = CLASSES_DIR
|
||||||
|
args.resources_dir = RESOURCES_DIR
|
||||||
|
args.type = 'Application'
|
||||||
|
args.name = 'test_name'
|
||||||
|
args.author = 'TestAuthor'
|
||||||
|
args.full_name = 'test.full.name.TestName'
|
||||||
|
args.tags = 'test, tag, Heat'
|
||||||
|
args.description = 'Test description'
|
||||||
|
args.ui = UI
|
||||||
|
args.logo = LOGO
|
||||||
|
prepared_files = ['UI', 'Classes', 'manifest.yaml',
|
||||||
|
'Resources', 'logo.png']
|
||||||
|
package_dir = mpl_package.prepare_package(args)
|
||||||
|
self.assertEqual(sorted(os.listdir(package_dir)),
|
||||||
|
sorted(prepared_files))
|
||||||
|
shutil.rmtree(package_dir)
|
184
muranoclient/tests/test_shell.py
Normal file
184
muranoclient/tests/test_shell.py
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import six
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import fixtures
|
||||||
|
import mock
|
||||||
|
from testtools import matchers
|
||||||
|
|
||||||
|
from muranoclient.openstack.common.apiclient import exceptions
|
||||||
|
import muranoclient.shell
|
||||||
|
from muranoclient.tests import base
|
||||||
|
|
||||||
|
FIXTURE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||||
|
'fixture_data'))
|
||||||
|
RESULT_PACKAGE = os.path.join(FIXTURE_DIR, 'test-app.zip')
|
||||||
|
|
||||||
|
FAKE_ENV = {'OS_USERNAME': 'username',
|
||||||
|
'OS_PASSWORD': 'password',
|
||||||
|
'OS_TENANT_NAME': 'tenant_name',
|
||||||
|
'OS_AUTH_URL': 'http://no.where'}
|
||||||
|
|
||||||
|
FAKE_ENV2 = {'OS_USERNAME': 'username',
|
||||||
|
'OS_PASSWORD': 'password',
|
||||||
|
'OS_TENANT_ID': 'tenant_id',
|
||||||
|
'OS_AUTH_URL': 'http://no.where'}
|
||||||
|
|
||||||
|
|
||||||
|
class ShellTest(base.TestCaseShell):
|
||||||
|
|
||||||
|
def make_env(self, exclude=None, fake_env=FAKE_ENV):
|
||||||
|
env = dict((k, v) for k, v in fake_env.items() if k != exclude)
|
||||||
|
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ShellTest, self).setUp()
|
||||||
|
self.useFixture(fixtures.MonkeyPatch(
|
||||||
|
'keystoneclient.v2_0.client.Client', mock.MagicMock))
|
||||||
|
|
||||||
|
def shell(self, argstr, exitcodes=(0,)):
|
||||||
|
orig = sys.stdout
|
||||||
|
orig_stderr = sys.stderr
|
||||||
|
try:
|
||||||
|
sys.stdout = six.StringIO()
|
||||||
|
sys.stderr = six.StringIO()
|
||||||
|
_shell = muranoclient.shell.MuranoShell()
|
||||||
|
_shell.main(argstr.split())
|
||||||
|
except SystemExit:
|
||||||
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
self.assertIn(exc_value.code, exitcodes)
|
||||||
|
finally:
|
||||||
|
stdout = sys.stdout.getvalue()
|
||||||
|
sys.stdout.close()
|
||||||
|
sys.stdout = orig
|
||||||
|
stderr = sys.stderr.getvalue()
|
||||||
|
sys.stderr.close()
|
||||||
|
sys.stderr = orig_stderr
|
||||||
|
return (stdout, stderr)
|
||||||
|
|
||||||
|
def test_help_unknown_command(self):
|
||||||
|
self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo')
|
||||||
|
|
||||||
|
def test_help(self):
|
||||||
|
required = [
|
||||||
|
'.*?^usage: murano',
|
||||||
|
'.*?^\s+package-create\s+Create an application package.',
|
||||||
|
'.*?^See "murano help COMMAND" for help on a specific command',
|
||||||
|
]
|
||||||
|
stdout, stderr = self.shell('help')
|
||||||
|
for r in required:
|
||||||
|
self.assertThat((stdout + stderr),
|
||||||
|
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
|
||||||
|
|
||||||
|
def test_help_on_subcommand(self):
|
||||||
|
required = [
|
||||||
|
'.*?^usage: murano package-create',
|
||||||
|
'.*?^Create an application package.',
|
||||||
|
]
|
||||||
|
stdout, stderr = self.shell('help package-create')
|
||||||
|
for r in required:
|
||||||
|
self.assertThat((stdout + stderr),
|
||||||
|
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
|
||||||
|
|
||||||
|
def test_help_no_options(self):
|
||||||
|
required = [
|
||||||
|
'.*?^usage: murano',
|
||||||
|
'.*?^\s+package-create\s+Create an application package',
|
||||||
|
'.*?^See "murano help COMMAND" for help on a specific command',
|
||||||
|
]
|
||||||
|
stdout, stderr = self.shell('')
|
||||||
|
for r in required:
|
||||||
|
self.assertThat((stdout + stderr),
|
||||||
|
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
|
||||||
|
|
||||||
|
def test_no_username(self):
|
||||||
|
required = ('You must provide a username via either --os-username or '
|
||||||
|
'env[OS_USERNAME] or a token via --os-auth-token or '
|
||||||
|
'env[OS_AUTH_TOKEN]',)
|
||||||
|
self.make_env(exclude='OS_USERNAME')
|
||||||
|
try:
|
||||||
|
self.shell('package-list')
|
||||||
|
except exceptions.CommandError as message:
|
||||||
|
self.assertEqual(required, message.args)
|
||||||
|
else:
|
||||||
|
self.fail('CommandError not raised')
|
||||||
|
|
||||||
|
def test_no_tenant_name(self):
|
||||||
|
required = ('You must provide a tenant name '
|
||||||
|
'or tenant id via --os-tenant-name, '
|
||||||
|
'--os-tenant-id, env[OS_TENANT_NAME] '
|
||||||
|
'or env[OS_TENANT_ID]',)
|
||||||
|
self.make_env(exclude='OS_TENANT_NAME')
|
||||||
|
try:
|
||||||
|
self.shell('package-list')
|
||||||
|
except exceptions.CommandError as message:
|
||||||
|
self.assertEqual(required, message.args)
|
||||||
|
else:
|
||||||
|
self.fail('CommandError not raised')
|
||||||
|
|
||||||
|
def test_no_tenant_id(self):
|
||||||
|
required = ('You must provide a tenant name '
|
||||||
|
'or tenant id via --os-tenant-name, '
|
||||||
|
'--os-tenant-id, env[OS_TENANT_NAME] '
|
||||||
|
'or env[OS_TENANT_ID]',)
|
||||||
|
self.make_env(exclude='OS_TENANT_ID', fake_env=FAKE_ENV2)
|
||||||
|
try:
|
||||||
|
self.shell('package-list')
|
||||||
|
except exceptions.CommandError as message:
|
||||||
|
self.assertEqual(required, message.args)
|
||||||
|
else:
|
||||||
|
self.fail('CommandError not raised')
|
||||||
|
|
||||||
|
def test_no_auth_url(self):
|
||||||
|
required = ('You must provide an auth url'
|
||||||
|
' via either --os-auth-url or via env[OS_AUTH_URL]',)
|
||||||
|
self.make_env(exclude='OS_AUTH_URL')
|
||||||
|
try:
|
||||||
|
self.shell('package-list')
|
||||||
|
except exceptions.CommandError as message:
|
||||||
|
self.assertEqual(required, message.args)
|
||||||
|
else:
|
||||||
|
self.fail('CommandError not raised')
|
||||||
|
|
||||||
|
def test_create_hot_based_package(self):
|
||||||
|
self.useFixture(fixtures.MonkeyPatch(
|
||||||
|
'muranoclient.v1.client.Client', mock.MagicMock))
|
||||||
|
heat_template = os.path.join(FIXTURE_DIR, 'heat-template.yaml')
|
||||||
|
logo = os.path.join(FIXTURE_DIR, 'logo.png')
|
||||||
|
self.make_env()
|
||||||
|
stdout, stderr = self.shell(
|
||||||
|
"package-create --template={0} "
|
||||||
|
"--output={1} -l={2}".format(heat_template, RESULT_PACKAGE, logo))
|
||||||
|
|
||||||
|
matchers.MatchesRegex((stdout + stderr),
|
||||||
|
"Application package "
|
||||||
|
"is available at {0}".format(RESULT_PACKAGE))
|
||||||
|
|
||||||
|
def test_create_mpl_package(self):
|
||||||
|
self.useFixture(fixtures.MonkeyPatch(
|
||||||
|
'muranoclient.v1.client.Client', mock.MagicMock))
|
||||||
|
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')
|
||||||
|
self.make_env()
|
||||||
|
stdout, stderr = self.shell(
|
||||||
|
"package-create -c={0} -r={1} -u={2} -o={3}".format(
|
||||||
|
classes_dir, resources_dir, ui, RESULT_PACKAGE))
|
||||||
|
matchers.MatchesRegex((stdout + stderr),
|
||||||
|
"Application package "
|
||||||
|
"is available at {0}".format(RESULT_PACKAGE))
|
0
muranoclient/v1/package_creator/__init__.py
Normal file
0
muranoclient/v1/package_creator/__init__.py
Normal file
95
muranoclient/v1/package_creator/hot_package.py
Normal file
95
muranoclient/v1/package_creator/hot_package.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# Copyright (c) 2014 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
import muranoclient
|
||||||
|
from muranoclient.openstack.common.apiclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def generate_manifest(args):
|
||||||
|
"""Generates application manifest file.
|
||||||
|
If some parameters are missed - they we be generated automatically.
|
||||||
|
:param args:
|
||||||
|
|
||||||
|
:returns: dictionary, contains manifest file data
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not os.path.isfile(args.template):
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"Template '{0}' doesn`t exist".format(args.template))
|
||||||
|
filename = os.path.basename(args.template)
|
||||||
|
if not args.name:
|
||||||
|
args.name = os.path.splitext(filename)[0]
|
||||||
|
if not args.full_name:
|
||||||
|
prefix = 'io.murano.apps.generated'
|
||||||
|
# normalized_name = re.sub(r'\W+', '_', args.name).title()
|
||||||
|
normalized_name = args.name.replace('_', ' ').title().replace(' ', '')
|
||||||
|
args.full_name = '{0}.{1}'.format(prefix, normalized_name)
|
||||||
|
try:
|
||||||
|
with open(args.template) as heat_file:
|
||||||
|
yaml_content = yaml.load(heat_file)
|
||||||
|
if not args.description:
|
||||||
|
args.description = yaml_content.get(
|
||||||
|
'description',
|
||||||
|
'Heat-defined application for a template "{0}"'.format(
|
||||||
|
filename))
|
||||||
|
except yaml.YAMLError:
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"Heat template, represented by --'template' parameter"
|
||||||
|
" should be a valid yaml file")
|
||||||
|
if not args.author:
|
||||||
|
args.author = args.os_username
|
||||||
|
if not args.tags:
|
||||||
|
args.tags = ['Heat-generated']
|
||||||
|
|
||||||
|
manifest = {
|
||||||
|
'Format': 'Heat.HOT/1.0',
|
||||||
|
'Type': 'Application',
|
||||||
|
'FullName': args.full_name,
|
||||||
|
'Name': args.name,
|
||||||
|
'Description': args.description,
|
||||||
|
'Author': args.author,
|
||||||
|
'Tags': args.tags
|
||||||
|
}
|
||||||
|
return manifest
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_package(args):
|
||||||
|
"""Compose required files for murano application package.
|
||||||
|
:param args: list of command line arguments
|
||||||
|
|
||||||
|
:returns: absolute path to directory with prepared files
|
||||||
|
"""
|
||||||
|
manifest = generate_manifest(args)
|
||||||
|
|
||||||
|
temp_dir = tempfile.mkdtemp()
|
||||||
|
manifest_file = os.path.join(temp_dir, 'manifest.yaml')
|
||||||
|
template_file = os.path.join(temp_dir, 'template.yaml')
|
||||||
|
|
||||||
|
logo_file = os.path.join(temp_dir, 'logo.png')
|
||||||
|
if not args.logo:
|
||||||
|
shutil.copyfile(muranoclient.get_resource('heatlogo.png'), logo_file)
|
||||||
|
else:
|
||||||
|
if os.path.isfile(args.logo):
|
||||||
|
shutil.copyfile(args.logo, logo_file)
|
||||||
|
|
||||||
|
with open(manifest_file, 'w') as f:
|
||||||
|
f.write(yaml.dump(manifest, default_flow_style=False))
|
||||||
|
shutil.copyfile(args.template, template_file)
|
||||||
|
|
||||||
|
return temp_dir
|
214
muranoclient/v1/package_creator/mpl_package.py
Normal file
214
muranoclient/v1/package_creator/mpl_package.py
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
# Copyright (c) 2014 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from muranoclient.common import utils
|
||||||
|
from muranoclient.openstack.common.apiclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_package(args):
|
||||||
|
"""Prepare all files and directories for that application package.
|
||||||
|
Generates manifest file and all required parameters for that.
|
||||||
|
|
||||||
|
:param args: list of command line arguments
|
||||||
|
|
||||||
|
:returns: absolute path to directory with prepared files
|
||||||
|
"""
|
||||||
|
if args.type and args.type not in ['Application', 'Library']:
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"--type should be set to 'Application' or 'Library'")
|
||||||
|
|
||||||
|
manifest = generate_manifest(args)
|
||||||
|
if args.type == 'Application':
|
||||||
|
if not args.ui:
|
||||||
|
raise exceptions.CommandError("'--ui' is required parameter")
|
||||||
|
if not os.path.exists(args.ui) or not os.path.isfile(args.ui):
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"{0} is not a file or doesn`t exist".format(args.ui))
|
||||||
|
|
||||||
|
temp_dir = tempfile.mkdtemp()
|
||||||
|
manifest_file = os.path.join(temp_dir, 'manifest.yaml')
|
||||||
|
classes_directory = os.path.join(temp_dir, 'Classes')
|
||||||
|
resource_directory = os.path.join(temp_dir, 'Resources')
|
||||||
|
|
||||||
|
with open(manifest_file, 'w') as f:
|
||||||
|
f.write(yaml.dump(manifest, default_flow_style=False))
|
||||||
|
|
||||||
|
if args.logo and os.path.isfile(args.logo):
|
||||||
|
logo_file = os.path.join(temp_dir, 'logo.png')
|
||||||
|
shutil.copyfile(args.logo, logo_file)
|
||||||
|
shutil.copytree(args.classes_dir, classes_directory)
|
||||||
|
shutil.copytree(args.resources_dir, resource_directory)
|
||||||
|
if args.ui:
|
||||||
|
ui_directory = os.path.join(temp_dir, 'UI')
|
||||||
|
os.mkdir(ui_directory)
|
||||||
|
shutil.copyfile(args.ui, os.path.join(ui_directory, 'ui.yaml'))
|
||||||
|
return temp_dir
|
||||||
|
|
||||||
|
|
||||||
|
def generate_manifest(args):
|
||||||
|
"""Generates application manifest file.
|
||||||
|
If some parameters are missed - they we be generated automatically.
|
||||||
|
:param args:
|
||||||
|
|
||||||
|
:returns: dictionary, contains manifest file data
|
||||||
|
"""
|
||||||
|
if not os.path.isdir(args.classes_dir):
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"'--classes-dir' parameter should be a directory")
|
||||||
|
if not os.path.isdir(args.resources_dir):
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"'--resources-dir' parameter should be a directory")
|
||||||
|
args = update_args(args)
|
||||||
|
if not args.type:
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"Too few arguments: --type and --full-name is required")
|
||||||
|
|
||||||
|
if not args.author:
|
||||||
|
args.author = args.os_username
|
||||||
|
if not args.description:
|
||||||
|
args.description = "Description for the application is not provided"
|
||||||
|
|
||||||
|
if not args.full_name:
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"Please, provide --full-name parameter")
|
||||||
|
|
||||||
|
manifest = {
|
||||||
|
'Format': 'MuranoPL/1.0',
|
||||||
|
'Type': args.type,
|
||||||
|
'FullName': args.full_name,
|
||||||
|
'Name': args.name,
|
||||||
|
'Description': args.description,
|
||||||
|
'Author': args.author,
|
||||||
|
'Classes': args.classes
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.tags:
|
||||||
|
manifest['Tags'] = args.tags
|
||||||
|
return manifest
|
||||||
|
|
||||||
|
|
||||||
|
def update_args(args):
|
||||||
|
"""Add and update arguments if possible.
|
||||||
|
Some parameters are not required and would be guessed
|
||||||
|
from muranoPL classes: thus, if class extends system application class
|
||||||
|
fully qualified and require names could be calculated.
|
||||||
|
Also, in that case type of a package could be set to 'Application'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
classes = {}
|
||||||
|
extends_from_application = False
|
||||||
|
for root, dirs, files in os.walk(args.classes_dir):
|
||||||
|
for class_file in files:
|
||||||
|
class_file_path = os.path.join(root, class_file)
|
||||||
|
try:
|
||||||
|
with open(class_file_path) as f:
|
||||||
|
content = yaml.load(f, utils.YaqlYamlLoader)
|
||||||
|
|
||||||
|
if not content.get('Name'):
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"Error in class definition: 'Name' "
|
||||||
|
"section is required")
|
||||||
|
class_name = get_fqn_for_name(content.get('Namespaces'),
|
||||||
|
content['Name'])
|
||||||
|
if root == args.classes_dir:
|
||||||
|
relative_path = class_file
|
||||||
|
else:
|
||||||
|
relative_path = os.path.join(
|
||||||
|
root.replace(args.classes_dir, "")[1:],
|
||||||
|
class_file)
|
||||||
|
classes[class_name] = relative_path
|
||||||
|
|
||||||
|
extends_from_application = check_derived_from_application(
|
||||||
|
content, extends_from_application)
|
||||||
|
if extends_from_application:
|
||||||
|
if not args.type:
|
||||||
|
args.type = 'Application'
|
||||||
|
if not args.name:
|
||||||
|
args.name = class_name.split('.')[-1]
|
||||||
|
if not args.full_name:
|
||||||
|
args.full_name = class_name
|
||||||
|
|
||||||
|
except yaml.YAMLError:
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"MuranoPL class {0} should be"
|
||||||
|
" a valid yaml file".format(class_file_path))
|
||||||
|
except IOError:
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"Could not open file {0}".format(class_file_path))
|
||||||
|
if not classes:
|
||||||
|
raise exceptions.CommandError("Application should have "
|
||||||
|
"at least one class")
|
||||||
|
args.classes = classes
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def get_fqn_for_name(namespaces, name):
|
||||||
|
"""Analyze name for namespace reference.
|
||||||
|
If namespaces are used - return a full name
|
||||||
|
:param namespaces: content of 'Namespaces' section of muranoPL class
|
||||||
|
:param name: name that should be checked
|
||||||
|
|
||||||
|
:returns: generated name according to namespaces
|
||||||
|
"""
|
||||||
|
values = name.split(':')
|
||||||
|
if len(values) == 1:
|
||||||
|
if '=' in namespaces:
|
||||||
|
return namespaces['='] + '.' + values[0]
|
||||||
|
return values[0]
|
||||||
|
if len(values) > 2:
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"Error in class definition: Wrong usage of ':' is "
|
||||||
|
"reserved for namespace referencing and could "
|
||||||
|
"be used only once "
|
||||||
|
"for each name")
|
||||||
|
if not namespaces:
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"Error in {0} class definition: "
|
||||||
|
"'Namespaces' section is missed")
|
||||||
|
|
||||||
|
result = namespaces.get(values[0])
|
||||||
|
if not result:
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"Error in class definition: namespaces "
|
||||||
|
"reference is not correct at the 'Extends'"
|
||||||
|
" section")
|
||||||
|
return result + '.' + values[1]
|
||||||
|
|
||||||
|
|
||||||
|
def check_derived_from_application(content, extends_from_application):
|
||||||
|
"""Look up for system 'io.murano.Application'
|
||||||
|
class in extends section.
|
||||||
|
"""
|
||||||
|
if content.get('Extends'):
|
||||||
|
extends = content['Extends']
|
||||||
|
if not isinstance(extends, list):
|
||||||
|
extends = [extends]
|
||||||
|
|
||||||
|
for name in extends:
|
||||||
|
parent_class_name = get_fqn_for_name(
|
||||||
|
content.get('Namespaces'),
|
||||||
|
name)
|
||||||
|
if parent_class_name == 'io.murano.Application':
|
||||||
|
if not extends_from_application:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"Murano package should have only one class"
|
||||||
|
" extends 'io.murano.Application' class")
|
||||||
|
return False
|
@ -12,10 +12,16 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
|
||||||
from muranoclient.common import exceptions
|
|
||||||
from muranoclient.common import utils
|
from muranoclient.common import utils
|
||||||
|
from muranoclient.openstack.common.apiclient import exceptions
|
||||||
|
from muranoclient.v1.package_creator import hot_package
|
||||||
|
from muranoclient.v1.package_creator import mpl_package
|
||||||
|
|
||||||
|
|
||||||
def do_environment_list(mc, args={}):
|
def do_environment_list(mc, args={}):
|
||||||
@ -75,7 +81,7 @@ def do_environment_show(mc, args):
|
|||||||
utils.print_dict(environment.to_dict(), formatters=formatters)
|
utils.print_dict(environment.to_dict(), formatters=formatters)
|
||||||
|
|
||||||
|
|
||||||
@utils.arg("environment_id",
|
@utils.arg("environment-id",
|
||||||
help="Environment id for which to list deployments")
|
help="Environment id for which to list deployments")
|
||||||
def do_deployment_list(mc, args):
|
def do_deployment_list(mc, args):
|
||||||
"""List deployments for an environment."""
|
"""List deployments for an environment."""
|
||||||
@ -107,12 +113,13 @@ def do_package_list(mc, args={}):
|
|||||||
utils.print_list(packages, fields, field_labels, sortby=0)
|
utils.print_list(packages, fields, field_labels, sortby=0)
|
||||||
|
|
||||||
|
|
||||||
@utils.arg("package_id",
|
@utils.arg("package-id",
|
||||||
help="Package ID to download")
|
help="Package ID to download")
|
||||||
@utils.arg("filename", metavar="file", nargs="?",
|
@utils.arg("filename", metavar="file", nargs="?",
|
||||||
help="Filename for download (defaults to stdout)")
|
help="Filename for download (defaults to stdout)")
|
||||||
def do_package_download(mc, args):
|
def do_package_download(mc, args):
|
||||||
"""Download a package to a filename or stdout."""
|
"""Download a package to a filename or stdout."""
|
||||||
|
|
||||||
def download_to_fh(package_id, fh):
|
def download_to_fh(package_id, fh):
|
||||||
fh.write(mc.packages.download(package_id))
|
fh.write(mc.packages.download(package_id))
|
||||||
|
|
||||||
@ -127,7 +134,7 @@ def do_package_download(mc, args):
|
|||||||
raise exceptions.CommandError("Package %s not found" % args.package_id)
|
raise exceptions.CommandError("Package %s not found" % args.package_id)
|
||||||
|
|
||||||
|
|
||||||
@utils.arg("package_id",
|
@utils.arg("package-id",
|
||||||
help="Package ID to show")
|
help="Package ID to show")
|
||||||
def do_package_show(mc, args):
|
def do_package_show(mc, args):
|
||||||
"""Display details for a package."""
|
"""Display details for a package."""
|
||||||
@ -158,7 +165,7 @@ def do_package_show(mc, args):
|
|||||||
utils.print_dict(to_display, formatters)
|
utils.print_dict(to_display, formatters)
|
||||||
|
|
||||||
|
|
||||||
@utils.arg("package_id",
|
@utils.arg("package-id",
|
||||||
help="Package ID to delete")
|
help="Package ID to delete")
|
||||||
def do_package_delete(mc, args):
|
def do_package_delete(mc, args):
|
||||||
"""Delete a package."""
|
"""Delete a package."""
|
||||||
@ -190,3 +197,62 @@ def do_service_show(mc, args):
|
|||||||
type=getattr(service, '?')['type']
|
type=getattr(service, '?')['type']
|
||||||
)
|
)
|
||||||
utils.print_dict(to_display)
|
utils.print_dict(to_display)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('-t', '--template', metavar='<HEAT_TEMPLATE>',
|
||||||
|
help='Path to the Heat template to import as '
|
||||||
|
'an Application Definition')
|
||||||
|
@utils.arg('-c', '--classes-dir', metavar='<CLASSES_DIRECTORY>',
|
||||||
|
help='Path to the directory containing application classes')
|
||||||
|
@utils.arg('-r', '--resources-dir', metavar='<RESOURCES_DIRECTORY>',
|
||||||
|
help='Path to the directory containing application resources')
|
||||||
|
@utils.arg('-n', '--name', metavar='<DISPLAY_NAME>',
|
||||||
|
help='Display name of the Application in Catalog')
|
||||||
|
@utils.arg('-f', '--full-name', metavar='<full-name>',
|
||||||
|
help='Fully-qualified name of the Application in Catalog')
|
||||||
|
@utils.arg('-a', '--author', metavar='<AUTHOR>', help='Name of the publisher')
|
||||||
|
@utils.arg('--tags', help='List of keywords connected to the application',
|
||||||
|
metavar='<TAG1 TAG2>', nargs='*')
|
||||||
|
@utils.arg('-d', '--description', metavar='<DESCRIPTION>',
|
||||||
|
help='Detailed description for the Application in Catalog')
|
||||||
|
@utils.arg('-o', '--output', metavar='<PACKAGE_NAME>',
|
||||||
|
help='The name of the output file archive to save locally')
|
||||||
|
@utils.arg('-u', '--ui', metavar='<UI_DEFINITION>',
|
||||||
|
help='Dynamic UI form definition')
|
||||||
|
@utils.arg('--type',
|
||||||
|
help='Package type. Possible values: Application or Library')
|
||||||
|
@utils.arg('-l', '--logo', metavar='<LOGO>', help='Path to the package logo')
|
||||||
|
def do_package_create(mc, args):
|
||||||
|
"""Create an application package."""
|
||||||
|
if args.template and (args.classes_dir or args.resources_dir):
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"Provide --template for a HOT-based package, OR --classes-dir"
|
||||||
|
" and --resources-dir for a MuranoPL-based package")
|
||||||
|
if not args.template and (not args.classes_dir or not args.resources_dir):
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"Provide --template for a HOT-based package, OR --classes-dir"
|
||||||
|
" and --resources-dir for a MuranoPL-based package")
|
||||||
|
directory_path = None
|
||||||
|
try:
|
||||||
|
if args.template:
|
||||||
|
directory_path = hot_package.prepare_package(args)
|
||||||
|
else:
|
||||||
|
directory_path = mpl_package.prepare_package(args)
|
||||||
|
|
||||||
|
archive_name = args.output or tempfile.mktemp(prefix="murano_")
|
||||||
|
|
||||||
|
_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)
|
||||||
|
|
||||||
|
|
||||||
|
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))
|
||||||
|
@ -9,3 +9,4 @@ Babel>=1.3
|
|||||||
pyOpenSSL>=0.11
|
pyOpenSSL>=0.11
|
||||||
requests>=1.1
|
requests>=1.1
|
||||||
PyYAML>=3.1.0
|
PyYAML>=3.1.0
|
||||||
|
yaql>=0.2.2,<0.3
|
||||||
|
Loading…
Reference in New Issue
Block a user