Adding security groups
Preface
- You’ll notice that the
basic-instance.ini
config file has two sections with erroneous server_groups. This was done to provide a quite test of the new script’s error-handling. - There are two new major functions added in
create-basic-instances-enhanced.py
:get_named_resource_list
- This takes a string and separates it into a list of resource names, which
are searched for using the existing
get_named_resource
function.
- This takes a string and separates it into a list of resource names, which
are searched for using the existing
map_or_list_contains_None
- This is function recursively examines a map a or list to check for any ‘None’ entries.
- This only works for our purposes because we omit
munch.Munch
objects, which are the output of the get_XXXX calls to the OpenStack SDK, when not None, and we are only interested in a resources was found (not None) or not found (is None). - It’s a bit clever, but also a bit of a hack, and may eventually be eliminated, although at present it’s a nice and easy way of achieving what we want.
- Note that we make having a security group a required config. While this is not required by the OpenStack SDK, OpenStack will automatically assign a ‘default’ security group if none are assigned, so making it an explicit config doesn’t have any change in behaviour.
- Also note that if we wanted to be lazy, we could just pass in list of
security group names, but we prefer to avoid exceptions on
create_server
where possible, and to handle missing resources a little more gracefully.
New Config and Scripts
basic-instance.ini
[DEFAULT]
cloud = ovh
username = user-XXXXXXXXXXX
image = user-base-0.3.2
flavor = d2-2
network = Ext-Net
[server1]
server_name = pytest001
security_groups = no_such_group
[server2]
server_name = pytest002
delete_if_exists = yes
[server3]
server_name = pytest003
delete_if_exists = yes
security_groups = :
create-basic-instances-enhanced.py
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
import sys
from typing import Collection, Mapping
from getpass import getpass
import configparser
import collections
import openstack
import munch
def get_named_resource(method, res_type_name, name):
print(
" Getting {res_type} named {res_name}".format(
res_type=res_type_name, res_name=name
)
)
value = method(name)
if value is None:
print(
" ** Failed to find {res_type} named {res_name}.".format(
res_type=res_type_name, res_name=name
),
file=sys.stderr,
)
return value
def get_named_resource_list(
method, res_type_name, name_list_str, sep=":", error_if_not_found=True
):
result_names = []
result_objects = []
name_list = name_list_str.split(sep)
if "" in name_list:
print(
" ** Empty {res_type_name}. Invalid config.".format(
res_type_name=res_type_name
),
file=sys.stderr,
)
result_names = None
result_objects = None
else:
for named_item in name_list:
item_munch = get_named_resource(method, res_type_name, named_item)
if item_munch is not None:
result_names.append(named_item)
result_objects.append(item_munch)
else:
if error_if_not_found:
result_names = None
result_objects = None
break
# else we just ignore the missing resource
return result_names, result_objects
def map_or_list_contains_None(maplist):
if maplist is None:
return True
elif isinstance(maplist, munch.Munch):
return False
elif isinstance(maplist, collections.abc.Mapping):
for __, value in maplist.items():
if map_or_list_contains_None(value):
return True
elif isinstance(maplist, list):
for value in maplist:
if map_or_list_contains_None(value):
return True
else:
return False
passwords = {}
config = configparser.ConfigParser(
defaults={
"delete_if_exists": "no",
"remember_password": "yes",
"userdata": "userdata-basic-instance.yaml",
"security_groups": "default"
}
)
readfile = config.read("basic-instance.ini")
if len(readfile) < 1:
print("Failed to read config file. Bailing...", file=sys.stderr)
sys.exit(1)
print("Creating {num_servers} servers".format(num_servers=(len(config.sections()))))
for section in config.sections():
sectmap = config[section]
print("Processing server {server_name}".format(server_name=sectmap["server_name"]))
username = sectmap["username"]
remember_password = sectmap.getboolean("remember_password")
delete_if_exists = sectmap.getboolean("delete_if_exists")
password = passwords.get(username)
if password is None:
password = getpass(
"Enter password for user {username}: ".format(username=username)
)
if remember_password:
passwords[username] = password
conn = openstack.connect(
cloud=sectmap["cloud"], username=username, password=password
)
server = conn.get_server(sectmap["server_name"], bare=True)
image = get_named_resource(conn.get_image, "boot image", sectmap["image"])
flavor = get_named_resource(conn.get_flavor, "instance flavor", sectmap["flavor"])
network = get_named_resource(
conn.get_network, "primary network", sectmap["network"]
)
security_groups, __ = get_named_resource_list(
conn.get_security_group, "security group", sectmap["security_groups"]
)
if (image is None) or (flavor is None) or (network is None):
print(" ** Skipping to next server due to missing resource", file=sys.stderr)
continue
if map_or_list_contains_None(security_groups):
print(
" ** Skipping to next server due to missing resource",
file=sys.stderr,
)
continue
print(" Getting userdata for instance")
try:
userdatafile = open(sectmap["userdata"], "r")
userdata = userdatafile.read()
userdatafile.close()
except FileNotFoundError:
print(
" ** Unable to find userdata file {userdata_file}. Skipping to next server.".format(
sectmap["userdata"]
),
file=sys.stderr,
)
continue
except IOError as e:
print(
" ** Unable to read userdata file {userdata_file}. Skipping to next server.".format(
sectmap["userdata"]
),
file=sys.stderr,
)
print("Error was {msg}".format(msg=e.strerror), file=sys.stderr)
continue
if server is not None:
if delete_if_exists:
print(
" Deleting existing server {server_name}, per delete_if_exists".format(
server_name=sectmap["server_name"]
)
)
if conn.delete_server(server, True, 600):
print(
" Server {server_name} successfully deleted".format(
server_name=sectmap["server_name"]
)
)
else:
print(
"Weird. Server {server_name} doesn't exist after all. Bailing...".format(
server_name=sectmap["server_name"]
),
file=sys.stderr,
)
sys.exit(1)
else:
print(
" Server {server_name} exists and delete_if_exists is False. Skipping to next server".format(
server_name=sectmap["server_name"]
)
)
continue
print(
" Creating server {server_name}".format(server_name=sectmap["server_name"])
)
server = conn.create_server(
sectmap["server_name"],
image=image,
flavor=flavor,
network=network,
userdata=userdata,
security_groups=security_groups,
wait=True,
timeout=600,
)
if server is not None:
print(
"Created server {server_name} with ip {ip}.".format(
server_name=server.name, ip=server.accessIPv4
)
)