Merge lp:~canonical-platform-qa/snappy-ecosystem-tests/adding-cloud-target into lp:snappy-ecosystem-tests

Proposed by Heber Parrucci
Status: Merged
Approved by: Heber Parrucci
Approved revision: 57
Merged at revision: 57
Proposed branch: lp:~canonical-platform-qa/snappy-ecosystem-tests/adding-cloud-target
Merge into: lp:snappy-ecosystem-tests
Diff against target: 523 lines (+374/-31)
10 files modified
README.rst (+34/-0)
requirements-setup.txt (+1/-0)
requirements.txt (+0/-1)
run_setup (+50/-17)
snappy_ecosystem_tests/environment/cloud/__init__.py (+19/-0)
snappy_ecosystem_tests/environment/cloud/data/__init__.py (+19/-0)
snappy_ecosystem_tests/environment/cloud/data/deployment.py (+25/-0)
snappy_ecosystem_tests/environment/cloud/driver.py (+169/-0)
snappy_ecosystem_tests/environment/managers.py (+17/-0)
snappy_ecosystem_tests/environment/setup.py (+40/-13)
To merge this branch: bzr merge lp:~canonical-platform-qa/snappy-ecosystem-tests/adding-cloud-target
Reviewer Review Type Date Requested Status
platform-qa-bot continuous-integration Approve
Sergio Cazzolato (community) Needs Fixing
Snappy ecosystem tests developer Pending
Review via email: mp+320534@code.launchpad.net

Commit message

Adding cloud target for virtual machines provisioning using juju.

Description of the change

The change is for adding cloud as a new target.
The idea is to provision virtual machines using juju.
The new setup using cloud target will deploy a charm and by default install snapd and snapcraft in different virtual machines pointing to staging.
The corresponding driver was implemented using subprocess because python juju clients have several bugs and limitations. I tested these clients:
- jujuclient
- juju

For now it assumes that the juju controller/model is already bootstrapped in the machine where the setup is executed. But it will be also automated soon.

@run_tests: snappy_ecosystem_tests/tests/test_snapd.py

To post a comment you must log in.
Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)
49. By Heber Parrucci

Adding missing requirement: pyyaml

Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Sergio Cazzolato (sergio-j-cazzolato) wrote :

Some comments inline.
Also you should check the juju version and stops execution in case it is not >v2

review: Needs Fixing
50. By Heber Parrucci

Addressing review comments

Revision history for this message
Heber Parrucci (heber013) wrote :

Comments addressed. Added checking of juju >= 2

Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)
51. By Heber Parrucci

Adding cloud setup instructions on README.rst

Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)
52. By Heber Parrucci

merge from trunk

Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)
53. By Heber Parrucci

* Removing specific revision to the charm url.
* Changing back default target to lxd.

Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)
54. By Heber Parrucci

merge from trunk

Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)
55. By Heber Parrucci

merge from trunk

Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)
56. By Heber Parrucci

increasing retries for juju deploy

Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Needs Fixing (continuous-integration)
57. By Heber Parrucci

adding proxy variable to juju deploy

Revision history for this message
platform-qa-bot (platform-qa-bot) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'README.rst'
--- README.rst 2017-02-24 19:27:01 +0000
+++ README.rst 2017-04-10 15:34:13 +0000
@@ -125,3 +125,37 @@
125$ bzr branch lp:snappy-ecosystem-tests125$ bzr branch lp:snappy-ecosystem-tests
126$ cd snappy-ecosystem-tests/126$ cd snappy-ecosystem-tests/
127$ python3 -m snappy_ecosystem_tests.snapd.staging_builder my_new_container_name 16.10127$ python3 -m snappy_ecosystem_tests.snapd.staging_builder my_new_container_name 16.10
128
129
130How run the tests in openstack
131==============================
132
1331- If you do not have a cloud/controller bootstrapped in your host, you can do it with a script in qakit:
134
135$ bzr branch lp:qakit; cd qakit
136$ ./qakit/juju/juju_bootstrap --cloud_name ^THE_NAME_YOU_LIKE^ --nova_file ^NOVA_CREDENTIALS_FILE^
137For example if you have canonistack credentials in $HOME/.canonistack/novarc:
138$ ./qakit/juju/juju_bootstrap --cloud_name canonistack --nova_file "$HOME/.canonistack/novarc"
139Enter the sudo password if asked (Some commands are executed as root and others do not).
140Note: You need to provide the full path for the nova file. You can use $HOME but not ~.
141It will install the needed packages and bootstrap the cloud.
142The controller name will be the same of the cloud.
143
144Important note: You need to run the bootstrap only once per machine per cloud.
145
1462- Once the bootstrap finishes properly, you need to setup the snappy-ecosystem environment in the cloud,
147For doing it, you should execute:
148./run_setup --model ^controller_name:admin/default^ --target cloud
149Note that if you do not pass the model, the default is: canonistack:admin/default
150It will create by default 2 machines with applications and needed dependencies:
151- snapcraft
152- snapd
153Both pointing to staging by default.
154If you would like to specify the charm when running the setup, you can do it with --charm ^CHARM-URL^.
155If not provided, it takes the default one.
156
1573- After setup finished properly. You should be able to run the tests:
158./run_system_tests ^YOUR_TESTS^ --target cloud
159
160Note that the step 1 (juju bootstrap) needs to be executed only once per machine per cloud.
161Then only will need to repeat the steps 2 and 3 when needed.
128162
=== modified file 'requirements-setup.txt'
--- requirements-setup.txt 2017-03-06 18:06:27 +0000
+++ requirements-setup.txt 2017-04-10 15:34:13 +0000
@@ -1,2 +1,3 @@
1pylxd1pylxd
2retrying2retrying
3pyyaml
34
=== modified file 'requirements.txt'
--- requirements.txt 2017-04-03 16:05:21 +0000
+++ requirements.txt 2017-04-10 15:34:13 +0000
@@ -18,4 +18,3 @@
18pyyaml18pyyaml
19retrying19retrying
20paramiko20paramiko
21#chromedriver_installer
2221
=== modified file 'run_setup'
--- run_setup 2017-03-15 17:11:23 +0000
+++ run_setup 2017-04-10 15:34:13 +0000
@@ -28,41 +28,74 @@
28 proxy="$2"28 proxy="$2"
29 shift # past argument29 shift # past argument
30 ;;30 ;;
31 -m|--mode)31 --target)
32 mode="$2"32 target="$2"
33 shift # past argument
34 ;;
35 --charm)
36 charm="$2"
37 shift # past argument
38 ;;
39 --model)
40 model="$2"
33 shift # past argument41 shift # past argument
34 ;;42 ;;
35 --profile)43 --profile)
36 profile="$2"44 profile="$2"
37 shift # past argument45 shift # past argument
38 ;;46 ;;
47 --series)
48 series="$2"
49 shift # past argument
50 ;;
51 --constraints)
52 constraints="$2"
53 shift # past argument
54 ;;
39esac55esac
40shift # past argument or value56shift # past argument or value
41done57done
4258
43. ./mk-venv -r requirements-setup.txt --proxy ${proxy}59. ./mk-venv -r requirements-setup.txt --proxy ${proxy}
4460
45if [ -z "$mode" ]; then61parameters='snappy_ecosystem_tests.environment.setup'
46 mode=lxd62
47fi63if [ ! -z ${target+x} ]; then
4864 parameters="$parameters --target $target"
49if [ -z "$profile" ]; then65fi
50 profile=staging66
51fi67if [ ! -z ${proxy+x} ]; then
5268 parameters="$parameters --proxy $proxy"
53if [ -z "$proxy" ]; then69fi
54 ve/bin/python3 -m snappy_ecosystem_tests.environment.setup --mode ${mode} --profile ${profile}70
55else71if [ ! -z ${charm+x} ]; then
56 ve/bin/python3 -m snappy_ecosystem_tests.environment.setup --mode ${mode} --profile ${profile} --proxy ${proxy}72 parameters="$parameters --charm $charm"
57fi73fi
5874
75if [ ! -z ${model+x} ]; then
76 parameters="$parameters --model $model"
77fi
78
79if [ ! -z ${profile+x} ]; then
80 parameters="$parameters --profile $profile"
81fi
82
83if [ ! -z ${series+x} ]; then
84 parameters="$parameters --series $series"
85fi
86
87if [ ! -z ${constraints+x} ]; then
88 parameters="$parameters --constraints $constraints"
89fi
90
91ve/bin/python3 -m ${parameters}
5992
60result=$?93result=$?
61deactivate94deactivate
6295
63if [ $result != 0 ]; then96if [ $result != 0 ]; then
64 echo -e -n "Environment setup FAILED.\n"97 echo -e -n "Environment setup FAILED.\n"
65 exit 1;98 exit 1
66else99else
67 echo -e -n "Environment setup PASSED.\n"100 echo -e -n "Environment setup PASSED.\n"
68fi101fi
69102
=== added directory 'snappy_ecosystem_tests/environment/cloud'
=== added file 'snappy_ecosystem_tests/environment/cloud/__init__.py'
--- snappy_ecosystem_tests/environment/cloud/__init__.py 1970-01-01 00:00:00 +0000
+++ snappy_ecosystem_tests/environment/cloud/__init__.py 2017-04-10 15:34:13 +0000
@@ -0,0 +1,19 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2
3#
4# Snappy Ecosystem Tests
5# Copyright (C) 2017 Canonical
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
020
=== added directory 'snappy_ecosystem_tests/environment/cloud/data'
=== added file 'snappy_ecosystem_tests/environment/cloud/data/__init__.py'
--- snappy_ecosystem_tests/environment/cloud/data/__init__.py 1970-01-01 00:00:00 +0000
+++ snappy_ecosystem_tests/environment/cloud/data/__init__.py 2017-04-10 15:34:13 +0000
@@ -0,0 +1,19 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2
3#
4# Snappy Ecosystem Tests
5# Copyright (C) 2017 Canonical
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
020
=== added file 'snappy_ecosystem_tests/environment/cloud/data/deployment.py'
--- snappy_ecosystem_tests/environment/cloud/data/deployment.py 1970-01-01 00:00:00 +0000
+++ snappy_ecosystem_tests/environment/cloud/data/deployment.py 2017-04-10 15:34:13 +0000
@@ -0,0 +1,25 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2
3#
4# Snappy Ecosystem Tests
5# Copyright (C) 2017 Canonical
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20"""Contain information for the applications to be deployed on the cloud"""
21
22APPLICATIONS = [
23 {'name': 'snapcraft', 'package': 'snapcraft', 'profile': 'staging'},
24 {'name': 'snapd', 'package': 'snapd', 'profile': 'staging'},
25]
026
=== added file 'snappy_ecosystem_tests/environment/cloud/driver.py'
--- snappy_ecosystem_tests/environment/cloud/driver.py 1970-01-01 00:00:00 +0000
+++ snappy_ecosystem_tests/environment/cloud/driver.py 2017-04-10 15:34:13 +0000
@@ -0,0 +1,169 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2
3#
4# Snappy Ecosystem Tests
5# Copyright (C) 2017 Canonical
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20
21"""Module Drive virtual machines in the cloud using juju"""
22
23import subprocess
24import tempfile
25
26import time
27import yaml
28from packaging import version
29
30
31class CloudDriver:
32
33 """Class to Drive virtual machines in the cloud using juju"""
34
35 STATUSES_NOT_READY = ['maintenance', 'waiting']
36
37 def launch(self, charm, applications, profile, model, proxy=None, **kwargs):
38 """Call to Deploy all the applications and then wait until all of them
39 are deployed
40
41 :param charm: The charm to deploy
42 :param applications: the applications to deploy
43 :param profile: the profile of the application to deploy
44 :param model: juju model in which the apps will be deployed
45 :param proxy: the proxy URL.
46 """
47 assert self.get_version('juju') >= version.parse('2'), \
48 'Juju >= 2 is required.'
49 subprocess.check_call(['juju', 'switch', model])
50 for app in applications:
51 self.deploy(charm, app, profile=profile, proxy=proxy, **kwargs)
52 self.wait_until_deployed(applications)
53
54 @staticmethod
55 def get_version(app):
56 """Return the version of the given app"""
57 app_version = subprocess.check_output(
58 [app, 'version']).decode().split('-')[0]
59 return version.parse(app_version)
60
61 def deploy(self, charm, app=None, profile=None, series=None,
62 constraints=None, proxy=None):
63 """
64 Launch a machine deploying the given charm to the connected juju model.
65
66 :param charm: The charm to deploy
67 :param app: the application name to deploy
68 :param profile: the profile of the application to deploy
69 :param series: series of the machine in which the application
70 will be deployed.
71 :param constraints: constraints for an application are set at deploy
72 time.
73 They must be expressed as a string containing only spaces
74 and key value pairs joined by an '='. Example: mem=8G cores=4
75 :param proxy: the proxy URL.
76 """
77 assert charm, 'You need to specify the charm to deploy'
78 command = ['juju', 'deploy', charm]
79 if series:
80 command.extend(['--series', series])
81 if constraints:
82 command.extend(['--constraints', ' '.join(constraints)])
83 if app:
84 if self.get_application_status(app['name']):
85 self.remove_application(app['name'])
86 app_config = {app['name']: {'package': app['package'],
87 'profile': profile}}
88 if proxy:
89 app_config[app['name']]['proxy'] = proxy
90 tmp_file = tempfile.NamedTemporaryFile()
91 with open(tmp_file.name, 'w+') as temp:
92 temp.write(yaml.dump(app_config))
93 command.extend(['--config', tmp_file.name, app['name']])
94 subprocess.check_call(command, universal_newlines=True)
95
96 def wait_until_deployed(self, apps, max_attempts=240, wait_period=5):
97 """
98 Wait until the given application is deployed.
99
100 :param apps: The applications list to check
101 :param max_attempts: Amount of attempts to check before failing
102 :param wait_period: The period of time in second to wait between checks
103 :raise Exception: If the max attempts were reached
104 """
105 for app in apps:
106 attempt = 0
107 status = self.get_application_status(app['name'])
108 while status in self.STATUSES_NOT_READY and attempt < max_attempts:
109 time.sleep(wait_period)
110 attempt += 1
111 status = self.get_application_status(app['name'])
112 if attempt == max_attempts:
113 raise Exception(
114 'Deploy for application %s has failed' % app['name'])
115
116 @staticmethod
117 def get_juju_status():
118 """
119 Get the status in dict format
120
121 :return: dict containing the juju status output
122 """
123 return yaml.load(subprocess.check_output(['juju', 'status',
124 '--format', 'yaml'],
125 universal_newlines=True))
126
127 def get_application_status(self, app_name):
128 """Get the application status or None if it cannot be found"""
129 status = self.get_juju_status()
130 try:
131 return status['applications'][app_name]['application-status'][
132 'current']
133 except (KeyError, TypeError):
134 return None
135
136 def remove_application(self, app_name, max_attempts=120, wait_period=5,
137 wait=True):
138 """Remove the given application
139
140 :param app_name: The application to remove
141 :param max_attempts: Amount of attempts to check before failing
142 :param wait_period: The period of time in second to wait between checks
143 :param wait: whether to wait until the application is actually removed
144 """
145 try:
146 subprocess.check_output(['juju', 'remove-application', app_name],
147 universal_newlines=True,
148 stderr=subprocess.STDOUT)
149 except subprocess.CalledProcessError as _e:
150 if 'not found' in _e.output:
151 return
152 else:
153 raise
154 attempt = 0
155 if wait:
156 status = self.get_application_status(app_name)
157 while status and attempt < max_attempts:
158 time.sleep(wait_period)
159 attempt += 1
160 status = self.get_application_status(app_name)
161 if attempt == max_attempts:
162 raise Exception('Deploy for application %s has failed' % app_name)
163
164 def get_ip(self, app_name):
165 """Get the ip of the Machine in which the application is deployed"""
166 status = self.get_juju_status()
167 for key, value in status['applications'][app_name]['units'].items():
168 if key.startswith(app_name):
169 return value['public-address']
0170
=== modified file 'snappy_ecosystem_tests/environment/managers.py'
--- snappy_ecosystem_tests/environment/managers.py 2017-03-27 20:21:15 +0000
+++ snappy_ecosystem_tests/environment/managers.py 2017-04-10 15:34:13 +0000
@@ -21,9 +21,15 @@
2121
22import logging22import logging
2323
24from snappy_ecosystem_tests.environment.cloud.driver import CloudDriver
25
24from snappy_ecosystem_tests.environment.constants import (26from snappy_ecosystem_tests.environment.constants import (
25 PROFILES, DEPENDENCIES)27 PROFILES, DEPENDENCIES)
26from snappy_ecosystem_tests.environment.containers.lxd import LXDDriver28from snappy_ecosystem_tests.environment.containers.lxd import LXDDriver
29from snappy_ecosystem_tests.environment.cloud.data.deployment import (
30 APPLICATIONS
31)
32
2733
28CONTAINERS = {34CONTAINERS = {
29 "snapd": {35 "snapd": {
@@ -65,3 +71,14 @@
65 packages=[PROFILES[profile][cont.name]["package_name"]],71 packages=[PROFILES[profile][cont.name]["package_name"]],
66 channel=PROFILES[profile][cont.name]["channel"])72 channel=PROFILES[profile][cont.name]["channel"])
67 self.driver.enable_ssh(cont)73 self.driver.enable_ssh(cont)
74
75
76class CloudManager:
77 """Manage machines in the cloud"""
78 def __init__(self):
79 self.driver = CloudDriver()
80
81 def setup(self, profile, charm=None, model=None, proxy=None, **kwargs):
82 """Setup virtual machines based on the profile"""
83 self.driver.launch(charm, APPLICATIONS, profile, model,
84 proxy=proxy, **kwargs)
6885
=== modified file 'snappy_ecosystem_tests/environment/setup.py'
--- snappy_ecosystem_tests/environment/setup.py 2017-03-27 20:21:15 +0000
+++ snappy_ecosystem_tests/environment/setup.py 2017-04-10 15:34:13 +0000
@@ -22,35 +22,62 @@
2222
23import argparse23import argparse
2424
25from snappy_ecosystem_tests.environment.managers import _LXDManager25from snappy_ecosystem_tests.environment.managers import (
26 _LXDManager,
27 CloudManager
28)
2629
2730
28SUPPORTED_MODULES = {31SUPPORTED_MODULES = {
29 "lxd": _LXDManager32 "lxd": _LXDManager,
33 "cloud": CloudManager,
30}34}
3135
3236
33def get_manager(mode):37def get_manager(target):
34 """Get the manager for the given mode"""38 """Get the manager for the given mode"""
35 try:39 try:
36 return SUPPORTED_MODULES[mode]()40 return SUPPORTED_MODULES[target]()
37 except KeyError:41 except KeyError:
38 raise RuntimeError("Mode: {} is not supported".format(mode))42 raise RuntimeError("Target: {} is not supported".format(target))
3943
4044
41def main(mode, profile, proxy):45def main(target, profile, **kwargs):
42 """Main script"""46 """Main script"""
43 manager = get_manager(mode)47 manager = get_manager(target)
44 manager.setup(profile, proxy)48 manager.setup(profile, **kwargs)
4549
4650
47if __name__ == '__main__':51if __name__ == '__main__':
48 PARSER = argparse.ArgumentParser(description=52 PARSER = argparse.ArgumentParser(description=
49 'Setup a snappy ecosystem environment.')53 'Setup a snappy ecosystem environment.')
50 PARSER.add_argument('--mode', default="lxd", help='lxd')54 PARSER.add_argument('--target', default="lxd", help='lxd')
51 PARSER.add_argument('--profile', default="staging",55 PARSER.add_argument('--profile', default="staging",
52 help='Profile to configure the environment')56 help='Profile to configure the environment')
53 PARSER.add_argument('--proxy', default=None,57 PARSER.add_argument('--proxy', default=None,
54 help='Proxy to use in the target machine/s')58 help='Proxy to use in the target machine/s')
55 ARGS = PARSER.parse_args()59 PARSER.add_argument('--constraints', default=None, nargs='+',
56 main(ARGS.mode, ARGS.profile, ARGS.proxy)60 help='constraints for creating machine/s')
61 ARGS, LEFT_OVER = PARSER.parse_known_args()
62 if ARGS.target == 'cloud':
63 CLOUD_PARSER = argparse.ArgumentParser(
64 description='Parse cloud specific arguments')
65 CLOUD_PARSER.add_argument('--charm',
66 default='cs:~heber013/snappy-ecosystem',
67 help='The juju charm to deploy')
68 CLOUD_PARSER.add_argument('--model',
69 default='canonistack:admin/default',
70 help='The juju model in which apps will'
71 ' be deployed')
72 CLOUD_PARSER.add_argument('--series',
73 help='The virtual machine series. '
74 'Example: xenial')
75 CLOUD_ARGS, LEFT_OVER = CLOUD_PARSER.parse_known_args()
76 main(ARGS.target, ARGS.profile,
77 charm=CLOUD_ARGS.charm,
78 model=CLOUD_ARGS.model,
79 series=CLOUD_ARGS.series,
80 constraints=ARGS.constraints,
81 proxy=ARGS.proxy)
82 else:
83 main(ARGS.target, ARGS.profile, proxy=ARGS.proxy)

Subscribers

People subscribed via source and target branches