Ported and updated launch script
Moved security rules and keypair creation into init first. Launch script now takes image name as positional argument, and name of instance as a named argument. This makes it work more like launch in other Canonical tools. Written in Python, for ease of maintenance. --retry and --wait args allow it to behave like tests expect it to, while humans will get a much more intuitive (and much less noisy) experience. Also increased time we wait for a ping on the host, to allow for slower, pure qemu, emulation times, and bring it in line with what Tempest does in similar situations. Change-Id: I11dcc098012468e9c88dcc7af78cde6920f31ecd
This commit is contained in:
parent
e007da2fb9
commit
0399955cf1
@ -1,7 +1,7 @@
|
|||||||
- job:
|
- job:
|
||||||
name: microstack-tox-snap-with-sudo
|
name: microstack-tox-snap-with-sudo
|
||||||
parent: openstack-tox-snap-with-sudo
|
parent: openstack-tox-snap-with-sudo
|
||||||
timeout: 7200
|
timeout: 5400
|
||||||
nodeset: ubuntu-bionic
|
nodeset: ubuntu-bionic
|
||||||
vars:
|
vars:
|
||||||
tox_envlist: snap
|
tox_envlist: snap
|
||||||
|
7
DEMO.md
7
DEMO.md
@ -114,10 +114,9 @@ sudo systemctl restart snap.microstack.*
|
|||||||
|
|
||||||
Create a test instance in your cloud.
|
Create a test instance in your cloud.
|
||||||
|
|
||||||
`microstack.launch test`
|
`microstack.launch cirros --name test`
|
||||||
|
|
||||||
This will launch a machine using the built-in cirros image, and also
|
This will launch a machine using the built-in cirros image. Once the
|
||||||
do some other nice things like setting up security groups. Once the
|
|
||||||
machine is setup, verify that you can ping it, then tear it down.
|
machine is setup, verify that you can ping it, then tear it down.
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -171,7 +170,7 @@ You'll need to load microstack credentials. You can temporarily drop
|
|||||||
into the microstack snap's shell environment to make this easy.
|
into the microstack snap's shell environment to make this easy.
|
||||||
|
|
||||||
```
|
```
|
||||||
snap run --shell microstack.launch
|
snap run --shell microstack.init
|
||||||
juju autoload-credentials
|
juju autoload-credentials
|
||||||
exit
|
exit
|
||||||
```
|
```
|
||||||
|
@ -28,7 +28,7 @@ To quickly configure networks and launch a vm, run
|
|||||||
|
|
||||||
This will configure various Openstack databases. Then run:
|
This will configure various Openstack databases. Then run:
|
||||||
|
|
||||||
`microstack.launch test`.
|
`microstack.launch cirros --name test`.
|
||||||
|
|
||||||
This will launch an instance for you, and make it available to manage via the command line, or via the Horizon Dashboard.
|
This will launch an instance for you, and make it available to manage via the command line, or via the Horizon Dashboard.
|
||||||
|
|
||||||
|
@ -17,29 +17,9 @@ else
|
|||||||
SERVER=$1
|
SERVER=$1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! $(openstack keypair list | grep "| microstack |") ]]; then
|
|
||||||
echo "creating keypair ($HOME/.ssh/id_microstack)"
|
|
||||||
mkdir -p $HOME/.ssh
|
|
||||||
chmod 700 $HOME/.ssh
|
|
||||||
openstack keypair create microstack > $HOME/.ssh/id_microstack
|
|
||||||
chmod 600 $HOME/.ssh/id_microstack
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Launching instance ..."
|
echo "Launching instance ..."
|
||||||
openstack server create --flavor m1.tiny --image cirros --nic net-id=test --key-name microstack $SERVER
|
openstack server create --flavor m1.tiny --image cirros --nic net-id=test --key-name microstack $SERVER
|
||||||
|
|
||||||
echo "Checking security groups ..."
|
|
||||||
SECGROUP_ID=`openstack security group list --project admin -f value -c ID`
|
|
||||||
if [[ ! $(openstack security group rule list | grep icmp | grep $SECGROUP_ID) ]]; then
|
|
||||||
echo "Creating security group rule for ping."
|
|
||||||
openstack security group rule create $SECGROUP_ID --proto icmp
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! $(openstack security group rule list | grep tcp | grep $SECGROUP_ID) ]]; then
|
|
||||||
echo "Creating security group rule for ssh."
|
|
||||||
openstack security group rule create $SECGROUP_ID --proto tcp --dst-port 22
|
|
||||||
fi
|
|
||||||
|
|
||||||
TRIES=0
|
TRIES=0
|
||||||
while [[ $(openstack server list | grep $SERVER | grep ERROR) ]]; do
|
while [[ $(openstack server list | grep $SERVER | grep ERROR) ]]; do
|
||||||
TRIES=$(($TRIES + 1))
|
TRIES=$(($TRIES + 1))
|
||||||
|
@ -15,6 +15,8 @@ snapctl set \
|
|||||||
questions.nova-setup=true \
|
questions.nova-setup=true \
|
||||||
questions.neutron-setup=true \
|
questions.neutron-setup=true \
|
||||||
questions.glance-setup=true \
|
questions.glance-setup=true \
|
||||||
|
questions.key-pair="id_microstack" \
|
||||||
|
questions.security-rules=true \
|
||||||
questions.post-setup=true \
|
questions.post-setup=true \
|
||||||
|
|
||||||
# MySQL snapshot for speedy install
|
# MySQL snapshot for speedy install
|
||||||
|
@ -303,7 +303,7 @@ apps:
|
|||||||
# Utility to launch a vm. Creates security groups, floating ips,
|
# Utility to launch a vm. Creates security groups, floating ips,
|
||||||
# and other necessities as well.
|
# and other necessities as well.
|
||||||
launch:
|
launch:
|
||||||
command: bin/launch.sh
|
command: microstack_launch
|
||||||
# plugs:
|
# plugs:
|
||||||
# - network
|
# - network
|
||||||
|
|
||||||
@ -820,3 +820,11 @@ parts:
|
|||||||
requirements:
|
requirements:
|
||||||
- requirements.txt # Relative to source path, so tools/init/req...txt
|
- requirements.txt # Relative to source path, so tools/init/req...txt
|
||||||
source: tools/init
|
source: tools/init
|
||||||
|
|
||||||
|
# Launch script
|
||||||
|
launch:
|
||||||
|
plugin: python
|
||||||
|
python-version: python3
|
||||||
|
requirements:
|
||||||
|
- requirements.txt
|
||||||
|
source: tools/launch
|
||||||
|
@ -56,7 +56,8 @@ class TestBasics(Framework):
|
|||||||
|
|
||||||
print("Testing microstack.launch ...")
|
print("Testing microstack.launch ...")
|
||||||
|
|
||||||
check(*self.PREFIX, launch, 'breakfast')
|
check(*self.PREFIX, launch, 'cirros', '--name', 'breakfast',
|
||||||
|
'--retry')
|
||||||
|
|
||||||
endpoints = check_output(
|
endpoints = check_output(
|
||||||
*self.PREFIX, '/snap/bin/microstack.openstack', 'endpoint', 'list')
|
*self.PREFIX, '/snap/bin/microstack.openstack', 'endpoint', 'list')
|
||||||
@ -82,8 +83,8 @@ class TestBasics(Framework):
|
|||||||
self.assertTrue(ip)
|
self.assertTrue(ip)
|
||||||
|
|
||||||
pings = 1
|
pings = 1
|
||||||
max_pings = 40
|
max_pings = 600 # ~10 minutes!
|
||||||
while not call(*self.PREFIX, 'ping', '-c', '1', ip):
|
while not call(*self.PREFIX, 'ping', '-c1', '-w1', ip):
|
||||||
pings += 1
|
pings += 1
|
||||||
if pings > max_pings:
|
if pings > max_pings:
|
||||||
self.assertFalse(True, msg='Max pings reached!')
|
self.assertFalse(True, msg='Max pings reached!')
|
||||||
@ -91,7 +92,7 @@ class TestBasics(Framework):
|
|||||||
print("Testing instances' ability to connect to the Internet")
|
print("Testing instances' ability to connect to the Internet")
|
||||||
# Test Internet connectivity
|
# Test Internet connectivity
|
||||||
attempts = 1
|
attempts = 1
|
||||||
max_attempts = 40
|
max_attempts = 300 # ~10 minutes!
|
||||||
username = check_output(*self.PREFIX, 'whoami')
|
username = check_output(*self.PREFIX, 'whoami')
|
||||||
|
|
||||||
while not call(
|
while not call(
|
||||||
@ -100,11 +101,11 @@ class TestBasics(Framework):
|
|||||||
'-oStrictHostKeyChecking=no',
|
'-oStrictHostKeyChecking=no',
|
||||||
'-i', '/home/{}/.ssh/id_microstack'.format(username),
|
'-i', '/home/{}/.ssh/id_microstack'.format(username),
|
||||||
'cirros@{}'.format(ip),
|
'cirros@{}'.format(ip),
|
||||||
'--', 'ping', '-c', '1', '91.189.94.250'):
|
'--', 'ping', '-c1', '91.189.94.250'):
|
||||||
attempts += 1
|
attempts += 1
|
||||||
if attempts > max_attempts:
|
if attempts > max_attempts:
|
||||||
self.assertFalse(True, msg='Unable to access the Internet!')
|
self.assertFalse(True, msg='Unable to access the Internet!')
|
||||||
time.sleep(5)
|
time.sleep(1)
|
||||||
|
|
||||||
if 'multipass' in self.PREFIX:
|
if 'multipass' in self.PREFIX:
|
||||||
print("Opening {}:80 up to the outside world".format(
|
print("Opening {}:80 up to the outside world".format(
|
||||||
|
@ -59,6 +59,8 @@ def main() -> None:
|
|||||||
questions.NovaSetup(),
|
questions.NovaSetup(),
|
||||||
questions.NeutronSetup(),
|
questions.NeutronSetup(),
|
||||||
questions.GlanceSetup(),
|
questions.GlanceSetup(),
|
||||||
|
questions.KeyPair(),
|
||||||
|
questions.SecurityRules(),
|
||||||
questions.PostSetup(),
|
questions.PostSetup(),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -68,6 +70,8 @@ def main() -> None:
|
|||||||
# allow people to pass in a config file from the command line.
|
# allow people to pass in a config file from the command line.
|
||||||
if CONTROL:
|
if CONTROL:
|
||||||
check('snapctl', 'set', 'questions.nova-setup=false')
|
check('snapctl', 'set', 'questions.nova-setup=false')
|
||||||
|
check('snapctl', 'set', 'questions.key-pair=nil')
|
||||||
|
check('snapctl', 'set', 'questions.security-rules=false')
|
||||||
|
|
||||||
if COMPUTE:
|
if COMPUTE:
|
||||||
check('snapctl', 'set', 'questions.rabbit-mq=false')
|
check('snapctl', 'set', 'questions.rabbit-mq=false')
|
||||||
|
@ -77,7 +77,7 @@ class Question():
|
|||||||
raise InvalidQuestion(
|
raise InvalidQuestion(
|
||||||
'Invalid type {} specified'.format(self._type))
|
'Invalid type {} specified'.format(self._type))
|
||||||
|
|
||||||
def _validate(self, answer: bytes) -> Tuple[str, bool]:
|
def _validate(self, answer: str) -> Tuple[str, bool]:
|
||||||
"""Validate an answer.
|
"""Validate an answer.
|
||||||
|
|
||||||
:param anwser: raw input from the user.
|
:param anwser: raw input from the user.
|
||||||
@ -89,7 +89,9 @@ class Question():
|
|||||||
return True, True
|
return True, True
|
||||||
|
|
||||||
if self._type == 'string':
|
if self._type == 'string':
|
||||||
# TODO Santize this!
|
# Allow people to negate a string by passing nil.
|
||||||
|
if answer.lower() == 'nil':
|
||||||
|
return None, True
|
||||||
return answer, True
|
return answer, True
|
||||||
|
|
||||||
# self._type is boolean
|
# self._type is boolean
|
||||||
|
@ -23,7 +23,7 @@ limitations under the License.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
@ -532,6 +532,67 @@ class GlanceSetup(Question):
|
|||||||
self._fetch_cirros()
|
self._fetch_cirros()
|
||||||
|
|
||||||
|
|
||||||
|
class KeyPair(Question):
|
||||||
|
"""Create a keypair for ssh access to instances.
|
||||||
|
|
||||||
|
TODO: split the asking from executing of questions, as ask about
|
||||||
|
this up front. (This needs to run at the end, but for user
|
||||||
|
experience reasons, we really want to ask all the non auto
|
||||||
|
questions at the beginning.)
|
||||||
|
"""
|
||||||
|
_type = 'string'
|
||||||
|
|
||||||
|
def yes(self, answer: str) -> None:
|
||||||
|
|
||||||
|
if 'microstack' not in check_output('openstack', 'keypair', 'list'):
|
||||||
|
log.info('Creating microstack keypair (~/.ssh/{})'.format(answer))
|
||||||
|
check('mkdir', '-p', '{HOME}/.ssh'.format(**_env))
|
||||||
|
check('chmod', '700', '{HOME}/.ssh'.format(**_env))
|
||||||
|
id_ = check_output('openstack', 'keypair', 'create', 'microstack')
|
||||||
|
id_path = '{HOME}/.ssh/{answer}'.format(
|
||||||
|
HOME=_env['HOME'],
|
||||||
|
answer=answer
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(id_path, 'w') as file_:
|
||||||
|
file_.write(id_)
|
||||||
|
check('chmod', '600', id_path)
|
||||||
|
# TODO: too many assumptions in the below. Make it portable!
|
||||||
|
user = _env['HOME'].split("/")[2]
|
||||||
|
check('chown', '{}:{}'.format(user, user), id_path)
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityRules(Question):
|
||||||
|
"""Setup default security rules."""
|
||||||
|
|
||||||
|
_type = 'boolean'
|
||||||
|
|
||||||
|
def yes(self, answer: str) -> None:
|
||||||
|
# Create security group rules
|
||||||
|
log.info('Creating security group rules ...')
|
||||||
|
group_id = check_output('openstack', 'security', 'group', 'list',
|
||||||
|
'--project', 'admin', '-f', 'value',
|
||||||
|
'-c', 'ID')
|
||||||
|
rules = check_output('openstack', 'security', 'group', 'rule', 'list',
|
||||||
|
'--format', 'json')
|
||||||
|
ping_rule = False
|
||||||
|
ssh_rule = False
|
||||||
|
|
||||||
|
for rule in json.loads(rules):
|
||||||
|
if rule['Security Group'] == group_id:
|
||||||
|
if rule['IP Protocol'] == 'icmp':
|
||||||
|
ping_rule = True
|
||||||
|
if rule['IP Protocol'] == 'tcp':
|
||||||
|
ssh_rule = True
|
||||||
|
|
||||||
|
if not ping_rule:
|
||||||
|
check('openstack', 'security', 'group', 'rule', 'create',
|
||||||
|
group_id, '--proto', 'icmp')
|
||||||
|
if not ssh_rule:
|
||||||
|
check('openstack', 'security', 'group', 'rule', 'create',
|
||||||
|
group_id, '--proto', 'tcp', '--dst-port', '22')
|
||||||
|
|
||||||
|
|
||||||
class PostSetup(Question):
|
class PostSetup(Question):
|
||||||
"""Sneak in any additional cleanup, then set the initialized state."""
|
"""Sneak in any additional cleanup, then set the initialized state."""
|
||||||
|
|
||||||
|
0
tools/launch/launch/__init__.py
Normal file
0
tools/launch/launch/__init__.py
Normal file
167
tools/launch/launch/main.py
Normal file
167
tools/launch/launch/main.py
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import petname
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
def check(*args: List[str]) -> int:
|
||||||
|
"""Execute a shell command, raising an error on failed excution.
|
||||||
|
|
||||||
|
:param args: strings to be composed into the bash call.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return subprocess.check_call(args, env=os.environ)
|
||||||
|
|
||||||
|
|
||||||
|
def check_output(*args: List[str]) -> str:
|
||||||
|
"""Execute a shell command, returning the output of the command.
|
||||||
|
|
||||||
|
:param args: strings to be composed into the bash call.
|
||||||
|
|
||||||
|
Include our env; pass in any extra keyword args.
|
||||||
|
"""
|
||||||
|
return subprocess.check_output(args, universal_newlines=True,
|
||||||
|
env=os.environ).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('image',
|
||||||
|
help='The name of the openstack image to use.')
|
||||||
|
parser.add_argument('-n', '--name', help='The name of the instance')
|
||||||
|
parser.add_argument('-k', '--key', help='ssh key to use',
|
||||||
|
default='microstack')
|
||||||
|
parser.add_argument('-f', '--flavor', help='Flavor to use.',
|
||||||
|
default='m1.tiny')
|
||||||
|
parser.add_argument('-t', '--net-id', help='Network', default='test')
|
||||||
|
parser.add_argument('-w', '--wait', action='store_true',
|
||||||
|
help='Wait for server to become active before exiting')
|
||||||
|
parser.add_argument('-r', '--retry', action='store_true',
|
||||||
|
help='Retry failed launch attempts')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def create_server(name, args):
|
||||||
|
|
||||||
|
ret = check_output('openstack', 'server', 'create',
|
||||||
|
'--flavor', args.flavor,
|
||||||
|
'--image', args.image,
|
||||||
|
'--nic', 'net-id={}'.format(args.net_id),
|
||||||
|
'--key-name', args.key,
|
||||||
|
name, '--format', 'json')
|
||||||
|
ret = json.loads(ret)
|
||||||
|
return ret['id']
|
||||||
|
|
||||||
|
|
||||||
|
def delete_server(server_id):
|
||||||
|
check('openstack', 'server', 'delete', server_id)
|
||||||
|
|
||||||
|
|
||||||
|
def check_server(name, server_id, args):
|
||||||
|
status = 'Unknown'
|
||||||
|
|
||||||
|
retries = 0
|
||||||
|
max_retries = 10
|
||||||
|
|
||||||
|
waits = 0
|
||||||
|
max_waits = 1000 # 100 seconds + ~1000 calls to `openstack server list`.
|
||||||
|
|
||||||
|
while True:
|
||||||
|
status_ = check_output('openstack', 'server', 'list',
|
||||||
|
'--format', 'json')
|
||||||
|
status_ = json.loads(status_)
|
||||||
|
for server in status_:
|
||||||
|
if server['ID'] == server_id:
|
||||||
|
status = server['Status']
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
# Something went wrong ...
|
||||||
|
break
|
||||||
|
|
||||||
|
if not args.wait and not args.retry:
|
||||||
|
# Just return BUILD or ACTIVE or Unknown.
|
||||||
|
break
|
||||||
|
|
||||||
|
if waits < 1:
|
||||||
|
print("Waiting for server to build ...")
|
||||||
|
|
||||||
|
if status == 'BUILD':
|
||||||
|
if waits <= max_waits:
|
||||||
|
waits += 1
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
# Looks like we're stuck! Fall through to ERROR check
|
||||||
|
# below.
|
||||||
|
status = 'BUILD (stuck)'
|
||||||
|
|
||||||
|
if status in ['ERROR', 'BUILD (stuck)']:
|
||||||
|
if not args.retry or retries > max_retries:
|
||||||
|
break
|
||||||
|
|
||||||
|
print('Ran into an error launching server. Retrying ...')
|
||||||
|
delete_server(server_id)
|
||||||
|
server_id = create_server(name, args)
|
||||||
|
waits = 0 # Reset waits
|
||||||
|
retries += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if status == 'ACTIVE':
|
||||||
|
break
|
||||||
|
|
||||||
|
return (status, server_id)
|
||||||
|
|
||||||
|
|
||||||
|
def launch(name, args):
|
||||||
|
"""Launch a server!"""
|
||||||
|
|
||||||
|
print("Launching server ...")
|
||||||
|
server_id = create_server(name, args)
|
||||||
|
|
||||||
|
status, server_id = check_server(name, server_id, args)
|
||||||
|
if status not in ['BUILD', 'ACTIVE']:
|
||||||
|
print('Uh-oh. Something went wrong launching {}. Status is {}.'.format(
|
||||||
|
name, status))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print('Allocating floating ip ...')
|
||||||
|
ip = check_output('openstack', 'floating', 'ip', 'create', '-f', 'value',
|
||||||
|
'-c', 'floating_ip_address', 'external')
|
||||||
|
check('openstack', 'server', 'add', 'floating', 'ip', server_id, ip)
|
||||||
|
|
||||||
|
print("""\
|
||||||
|
Server {} launched! (status is {})
|
||||||
|
|
||||||
|
Access it with `ssh -i \
|
||||||
|
$HOME/.ssh/id_microstack` <username>@{}""".format(name, status, ip))
|
||||||
|
|
||||||
|
gate = check_output('snapctl', 'get', 'questions.ext-gateway')
|
||||||
|
print('You can also visit the OpenStack dashboard at http://{}'.format(
|
||||||
|
gate))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = parse_args()
|
||||||
|
name = args.name or petname.generate()
|
||||||
|
|
||||||
|
# Parse microstack.rc
|
||||||
|
# TODO: we need a share lib that does this in a more robust way.
|
||||||
|
mstackrc = '{SNAP_COMMON}/etc/microstack.rc'.format(**os.environ)
|
||||||
|
with open(mstackrc, 'r') as rc_file:
|
||||||
|
for line in rc_file.readlines():
|
||||||
|
if not line.startswith('export'):
|
||||||
|
continue
|
||||||
|
key, val = line[7:].split('=')
|
||||||
|
os.environ[key.strip()] = val.strip()
|
||||||
|
|
||||||
|
return launch(name, args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
1
tools/launch/requirements.txt
Normal file
1
tools/launch/requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
petname
|
13
tools/launch/setup.py
Normal file
13
tools/launch/setup.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="microstack_launch",
|
||||||
|
description="Launch an instance!",
|
||||||
|
packages=find_packages(exclude=("tests",)),
|
||||||
|
version="0.0.1",
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'microstack_launch = launch.main:main',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
7
tox.ini
7
tox.ini
@ -56,6 +56,13 @@ commands =
|
|||||||
{toxinidir}/tests/test_basic.py
|
{toxinidir}/tests/test_basic.py
|
||||||
{toxinidir}/tests/test_control.py
|
{toxinidir}/tests/test_control.py
|
||||||
|
|
||||||
|
[testenv:lint]
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
commands =
|
||||||
|
flake8 {toxinidir}/tests/
|
||||||
|
flake8 {toxinidir}/tools/init/init/
|
||||||
|
flake8 {toxinidir}/tools/launch/launch/
|
||||||
|
|
||||||
[testenv:init_lint]
|
[testenv:init_lint]
|
||||||
deps = -r{toxinidir}/tools/init/test-requirements.txt
|
deps = -r{toxinidir}/tools/init/test-requirements.txt
|
||||||
-r{toxinidir}/tools/init/requirements.txt
|
-r{toxinidir}/tools/init/requirements.txt
|
||||||
|
Loading…
Reference in New Issue
Block a user