Added custom scripts from other repo to running version
This commit is contained in:
parent
0d8d11c51c
commit
89ba1aad1e
2 changed files with 470 additions and 1 deletions
|
@ -39,7 +39,7 @@ services:
|
|||
- netbox-nginx-config:/etc/netbox-nginx/:ro
|
||||
- /etc/cert-client:/etc/cert-client:ro
|
||||
postgres:
|
||||
image: postgres:11-alpine
|
||||
image: postgres:10.4-alpine
|
||||
env_file: env/postgres.env
|
||||
volumes:
|
||||
- netbox-postgres-data:/var/lib/postgresql/data
|
||||
|
|
469
scripts/create_vms.py
Normal file
469
scripts/create_vms.py
Normal file
|
@ -0,0 +1,469 @@
|
|||
import netaddr
|
||||
|
||||
from dcim.choices import InterfaceTypeChoices, InterfaceModeChoices
|
||||
from dcim.models import Platform, DeviceRole, Site
|
||||
from ipam.models import IPAddress, VRF, Interface, Prefix, VLAN
|
||||
from tenancy.models import Tenant
|
||||
from virtualization.models import VirtualMachine, Cluster
|
||||
from virtualization.choices import VirtualMachineStatusChoices
|
||||
from extras.scripts import Script, ObjectVar, ChoiceVar, TextVar, IntegerVar
|
||||
from extras.models import Tag
|
||||
from utilities.forms import APISelect
|
||||
|
||||
|
||||
class DeployVM(Script):
|
||||
|
||||
env = ""
|
||||
interfaces = []
|
||||
time_zone = ""
|
||||
dns_domain_private = ""
|
||||
dns_domain_public = ""
|
||||
dns_servers = []
|
||||
ntp_servers = []
|
||||
ssh_authorized_keys = []
|
||||
ssh_port = ""
|
||||
_default_interface = {}
|
||||
tags = []
|
||||
output = []
|
||||
success_log = ""
|
||||
|
||||
class Meta:
|
||||
name = "Deploy new VMs"
|
||||
description = "Deploy new virtual machines from existing platforms and roles using AWX"
|
||||
fields = ['persist_disk', 'status', 'health_check', 'serial', 'tenant', 'cluster', 'env', 'untagged_vlan', 'backup', 'ip_addresses', 'vm_count', 'vcpus', 'memory', 'platform', 'role', 'disk', 'ssh_authorized_keys', 'hostnames']
|
||||
field_order = ['status', 'tenant', 'cluster', 'env', 'platform', 'role', 'health_check', 'serial', 'persist_disk', 'backup', 'vm_count', 'vcpus', 'memory', 'disk', 'hostnames', 'untagged_vlan', 'ip_addresses', 'ssh_authorized_keys']
|
||||
commit_default = False
|
||||
|
||||
health_check = ChoiceVar(
|
||||
label="Health checks on deployment",
|
||||
description="Deployment will fail if server does not pass Consul health checks",
|
||||
required=True,
|
||||
choices=(
|
||||
('True', 'Yes'),
|
||||
('False', 'No')
|
||||
)
|
||||
)
|
||||
|
||||
serial = ChoiceVar(
|
||||
label="Serial deployment",
|
||||
description="VM will not be parallelized in deployment",
|
||||
required=True,
|
||||
choices=(
|
||||
('False', 'No'),
|
||||
('True', 'Yes'),
|
||||
)
|
||||
)
|
||||
|
||||
persist_disk = ChoiceVar(
|
||||
label="Persist volume on redeploy",
|
||||
description="VM will persist disk2 in vSphere on redeploys",
|
||||
required=True,
|
||||
choices=(
|
||||
('False', 'No'),
|
||||
('True', 'Yes'),
|
||||
)
|
||||
)
|
||||
|
||||
status = ChoiceVar(
|
||||
label="VM Status",
|
||||
description="Deploy VM now or later?",
|
||||
required=True,
|
||||
choices=(
|
||||
(VirtualMachineStatusChoices.STATUS_STAGED, 'Staged (Deploy now)'),
|
||||
(VirtualMachineStatusChoices.STATUS_PLANNED, 'Planned (Save for later)')
|
||||
)
|
||||
)
|
||||
|
||||
tenant = ObjectVar(
|
||||
default="patientsky-hosting",
|
||||
description="Name of the tenant the VMs beloing to",
|
||||
queryset=Tenant.objects.filter()
|
||||
)
|
||||
|
||||
cluster = ObjectVar(
|
||||
default="odn1",
|
||||
description="Name of the vSphere cluster you are deploying to",
|
||||
queryset=Cluster.objects.all()
|
||||
)
|
||||
|
||||
env = ChoiceVar(
|
||||
label="Environment",
|
||||
description="Environment to deploy VM",
|
||||
default="vlb",
|
||||
choices=(
|
||||
('pno', 'pno'),
|
||||
('inf', 'inf'),
|
||||
('stg', 'stg'),
|
||||
('dev', 'dev'),
|
||||
('hem', 'hem'),
|
||||
('hov', 'hov'),
|
||||
('hpl', 'hpl'),
|
||||
('mgt', 'mgt'),
|
||||
('cse', 'cse'),
|
||||
('qua', 'qua'),
|
||||
('dmo', 'dmo'),
|
||||
('vlb', 'vlb'),
|
||||
('cmi', 'cmi')
|
||||
)
|
||||
)
|
||||
|
||||
platform = ObjectVar(
|
||||
description="Host OS to deploy",
|
||||
queryset=Platform.objects.filter(
|
||||
name__regex=r'^(base_.*)'
|
||||
).order_by('name')
|
||||
)
|
||||
|
||||
role = ObjectVar(
|
||||
label="VM Role",
|
||||
description="VM Role",
|
||||
queryset=DeviceRole.objects.filter(
|
||||
vm_role=True
|
||||
).order_by('name')
|
||||
)
|
||||
|
||||
backup = ChoiceVar(
|
||||
label="Backup strategy",
|
||||
description="The backup strategy deployed to this VM with Veeam",
|
||||
required=True,
|
||||
choices=(
|
||||
('nobackup', 'Never'),
|
||||
('backup_general_1', 'Daily'),
|
||||
('backup_general_4', 'Monthly')
|
||||
)
|
||||
)
|
||||
|
||||
ip_addresses = TextVar(
|
||||
required=False,
|
||||
label="IP Addresses",
|
||||
description="List of IP addresses to create w. prefix e.g 192.168.0.10/24. If none given, hosts will be assigned IPs 'automagically'"
|
||||
)
|
||||
|
||||
untagged_vlan = ObjectVar(
|
||||
required=False,
|
||||
label="VLAN",
|
||||
widget=APISelect(api_url='/api/ipam/vlans/', display_field='display_name'),
|
||||
queryset=VLAN.objects.all(),
|
||||
description="Choose VLAN for IP-addresses",
|
||||
)
|
||||
|
||||
hostnames = TextVar(
|
||||
required=True,
|
||||
label="Hostnames",
|
||||
description="List of hostnames to create."
|
||||
)
|
||||
|
||||
ssh_authorized_keys = TextVar(
|
||||
label="SSH Authorized Keys",
|
||||
required=False,
|
||||
description="List of accepted SSH keys - defaults to site config context 'ssh_authorized_keys'"
|
||||
)
|
||||
|
||||
vm_count = ChoiceVar(
|
||||
label="Number of VMs",
|
||||
description="Number of VMs to deploy",
|
||||
choices=(
|
||||
('1', '1'),
|
||||
('2', '2'),
|
||||
('3', '3'),
|
||||
('4', '4'),
|
||||
('5', '5'),
|
||||
('6', '6'),
|
||||
('7', '7'),
|
||||
('8', '8'),
|
||||
('9', '9'),
|
||||
('10', '10')
|
||||
)
|
||||
)
|
||||
|
||||
vcpus = ChoiceVar(
|
||||
label="Number of CPUs",
|
||||
description="Number of virtual CPUs",
|
||||
default="2",
|
||||
choices=(
|
||||
('1', '1'),
|
||||
('2', '2'),
|
||||
('3', '3'),
|
||||
('4', '4'),
|
||||
('5', '5'),
|
||||
('6', '6'),
|
||||
('7', '7'),
|
||||
('8', '8')
|
||||
)
|
||||
)
|
||||
|
||||
memory = ChoiceVar(
|
||||
description="Amount of VM memory",
|
||||
default="4096",
|
||||
choices=(
|
||||
('1024', '1024'),
|
||||
('2048', '2048'),
|
||||
('4096', '4096'),
|
||||
('8192', '8192'),
|
||||
('16384', '16384'),
|
||||
('32768', '32768')
|
||||
)
|
||||
)
|
||||
|
||||
disk = IntegerVar(
|
||||
label="Disk size",
|
||||
description="Disk size in GB",
|
||||
default="20"
|
||||
)
|
||||
|
||||
def appendLogSuccess(self, log: str, obj=None):
|
||||
self.success_log += " {} `\n{}\n`".format(log, obj)
|
||||
return self
|
||||
|
||||
def flushLogSuccess(self):
|
||||
self.log_success(self.success_log)
|
||||
self.success_log = ""
|
||||
return self
|
||||
|
||||
def _generateHostname(self, cluster, env, descriptor):
|
||||
|
||||
# I now proclaim this VM, First of its Name, Queen of the Andals and the First Men, Protector of the Seven Kingdoms
|
||||
vm_index = "001"
|
||||
|
||||
search_for = str(cluster) + '-' + env + '-' + descriptor + '-'
|
||||
vms = VirtualMachine.objects.filter(
|
||||
name__startswith=search_for
|
||||
)
|
||||
|
||||
if len(vms) > 0:
|
||||
# Get last of its kind
|
||||
last_vm_index = int(vms[len(vms) - 1].name.split('-')[3]) + 1
|
||||
if last_vm_index < 10:
|
||||
vm_index = '00' + str(last_vm_index)
|
||||
elif last_vm_index < 100:
|
||||
vm_index = '0' + str(last_vm_index)
|
||||
else:
|
||||
vm_index = str(last_vm_index)
|
||||
|
||||
hostname = str(cluster) + '-' + env + '-' + descriptor + '-' + vm_index
|
||||
|
||||
return hostname
|
||||
|
||||
def __validateInput(self, data, base_context_data):
|
||||
|
||||
try:
|
||||
self.interfaces = base_context_data['interfaces']
|
||||
self.time_zone = base_context_data['time_zone']
|
||||
self.tags = base_context_data['tags']
|
||||
self.dns_domain_private = base_context_data['dns_domain_private']
|
||||
self.dns_domain_public = base_context_data['dns_domain_public']
|
||||
self.dns_servers = base_context_data['dns_servers']
|
||||
self.ntp_servers = base_context_data['ntp_servers']
|
||||
self.ssh_authorized_keys = base_context_data['ssh_authorized_keys']
|
||||
self.ssh_port = base_context_data['ssh_port']
|
||||
self.tags.update({'env_' + self.env: {'comments': 'Environment', 'color': '009688'}})
|
||||
self.tags.update({'vsphere_' + data['backup']: {'comments': 'Backup strategy', 'color': '009688'}})
|
||||
if (data['health_check'] == 'True'):
|
||||
self.tags.update({'health_check': {'comments': 'Do health checks in deployment', 'color': '4caf50'}})
|
||||
if (data['serial'] == 'True'):
|
||||
self.tags.update({'serial': {'comments': 'Do health checks in deployment', 'color': '4caf50'}})
|
||||
except Exception as error:
|
||||
self.log_failure("Error when parsing context_data! Error: " + str(error))
|
||||
return False
|
||||
|
||||
if self.interfaces is None:
|
||||
self.log_failure("No interfaces object in context data!")
|
||||
return False
|
||||
|
||||
if data['ip_addresses'] != "" and len(data['ip_addresses'].splitlines()) != int(data['vm_count']):
|
||||
self.log_failure("The number of IP addresses and VMs does not match!")
|
||||
return False
|
||||
|
||||
if data['hostnames'] != "" and len(data['hostnames'].splitlines()) != int(data['vm_count']):
|
||||
self.log_failure("The number of hostnames and VMs does not match!")
|
||||
return False
|
||||
|
||||
if self.interfaces is None:
|
||||
self.log_failure("No interfaces object in context data!")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def run(self, data):
|
||||
|
||||
vrf = VRF.objects.get(
|
||||
name="global"
|
||||
)
|
||||
|
||||
self.env = data['env']
|
||||
|
||||
# Setup base virtual machine for copying config_context
|
||||
base_vm = VirtualMachine(
|
||||
cluster=data['cluster'],
|
||||
platform=data['platform'],
|
||||
role=data['role'],
|
||||
tenant=data['tenant'],
|
||||
name="script_temp"
|
||||
)
|
||||
|
||||
base_vm.save()
|
||||
|
||||
if self.__validateInput(data=data, base_context_data=base_vm.get_config_context()) is False:
|
||||
return False
|
||||
|
||||
# Delete base virtual machine for copying config_context
|
||||
vm_delete = VirtualMachine.objects.get(
|
||||
name="script_temp"
|
||||
)
|
||||
vm_delete.delete()
|
||||
|
||||
for i in range(0, int(data['vm_count'])):
|
||||
|
||||
hostnames = data['hostnames'].splitlines()
|
||||
ip_addresses = data['ip_addresses'].splitlines()
|
||||
|
||||
if len(hostnames) > 0:
|
||||
hostname = hostnames[i]
|
||||
else:
|
||||
hostname = self._generateHostname(
|
||||
cluster=data['cluster'].name,
|
||||
env=self.env,
|
||||
descriptor="na"
|
||||
)
|
||||
|
||||
# Check if VM exists
|
||||
if len(VirtualMachine.objects.filter(name=hostname)) > 0:
|
||||
self.log_failure("VM with hostname " + hostname + " already exists!")
|
||||
return False
|
||||
|
||||
if len(ip_addresses) > 0:
|
||||
|
||||
# Check if IP address exists
|
||||
ip_check = IPAddress.objects.filter(
|
||||
address=ip_addresses[i]
|
||||
)
|
||||
|
||||
if len(ip_check) > 0:
|
||||
self.log_failure(str(ip_check[0].address) + ' is already assigned to ' + str(ip_check[0].interface.name))
|
||||
return False
|
||||
|
||||
domain = self.dns_domain_private if netaddr.IPNetwork(ip_addresses[i]).is_private() is True else self.dns_domain_public
|
||||
ip_address = IPAddress(
|
||||
address=ip_addresses[i],
|
||||
vrf=vrf,
|
||||
tenant=data['tenant'],
|
||||
family=4,
|
||||
dns_name=hostname + '.' + domain,
|
||||
)
|
||||
|
||||
ip_address.save()
|
||||
self.appendLogSuccess(log="Created IP", obj=ip_addresses[i]).appendLogSuccess(log="DNS", obj=hostname + '.' + domain)
|
||||
else:
|
||||
if data['untagged_vlan'] is not None:
|
||||
|
||||
try:
|
||||
|
||||
# Auto assign IPs from vsphere_port_group
|
||||
prefix = Prefix.objects.get(
|
||||
vlan=data['untagged_vlan'],
|
||||
site=Site.objects.get(name=data['cluster'].site.name),
|
||||
is_pool=True
|
||||
)
|
||||
|
||||
ip = prefix.get_first_available_ip()
|
||||
|
||||
domain = self.dns_domain_private if netaddr.IPNetwork(ip).is_private() is True else self.dns_domain_public
|
||||
ip_address = IPAddress(
|
||||
address=ip,
|
||||
vrf=vrf,
|
||||
tenant=data['tenant'],
|
||||
family=4,
|
||||
dns_name=hostname + '.' + domain,
|
||||
)
|
||||
|
||||
ip_address.save()
|
||||
self.appendLogSuccess(log="Auto-assigned IP", obj=ip).appendLogSuccess(log="DNS", obj=hostname + '.' + domain)
|
||||
except Exception:
|
||||
self.log_failure("An error occurred while auto-assigning IP address. VLAN or Prefix not found!")
|
||||
return False
|
||||
else:
|
||||
self.log_failure("No IP address choice was made")
|
||||
return False
|
||||
|
||||
vm = VirtualMachine(
|
||||
status=data['status'],
|
||||
cluster=data['cluster'],
|
||||
platform=data['platform'],
|
||||
role=data['role'],
|
||||
tenant=data['tenant'],
|
||||
name=hostname,
|
||||
disk=data['disk'],
|
||||
memory=data['memory'],
|
||||
vcpus=data['vcpus']
|
||||
)
|
||||
|
||||
vm.primary_ip4 = ip_address
|
||||
vm.save()
|
||||
self.appendLogSuccess(log="for VM in ", obj=vm.cluster)
|
||||
|
||||
self.__assignTags(vm=vm)
|
||||
|
||||
# Assign IP to interface
|
||||
if self.__setupInterface(ip_address=ip_address, data=data, vm=vm) is False:
|
||||
return False
|
||||
|
||||
self.flushLogSuccess()
|
||||
|
||||
return self.success_log
|
||||
|
||||
def __assignTags(self, vm: VirtualMachine):
|
||||
"""
|
||||
Assign tags from context data
|
||||
"""
|
||||
for tag in self.tags:
|
||||
if len(Tag.objects.filter(name=tag)) == 0:
|
||||
color = self.tags[tag]['color'] if 'color' in self.tags[tag] else '9e9e9e'
|
||||
comments = self.tags[tag]['comments'] if 'comments' in self.tags[tag] else 'No comments'
|
||||
newTag = Tag(comments=comments, name=tag, color=color)
|
||||
newTag.save()
|
||||
|
||||
vm.tags.add(tag)
|
||||
|
||||
vm.save()
|
||||
|
||||
def __setupInterface(self, ip_address: IPAddress, vm: VirtualMachine, data):
|
||||
"""
|
||||
Setup interface and add IP address
|
||||
"""
|
||||
|
||||
# Get net address tools
|
||||
ip = netaddr.IPNetwork(ip_address.address)
|
||||
prefix_search = str(ip.network) + '/' + str(ip.prefixlen)
|
||||
|
||||
try:
|
||||
prefix = Prefix.objects.get(
|
||||
prefix=prefix_search,
|
||||
is_pool=True
|
||||
)
|
||||
except Exception:
|
||||
self.log_failure("Prefix for IP " + ip_address.address + " was not found")
|
||||
return False
|
||||
|
||||
# Right now we dont support multiple nics. But the data model supports it
|
||||
interface = Interface(
|
||||
name=self.interfaces['nic0']['name'],
|
||||
mtu=self.interfaces['nic0']['mtu'],
|
||||
virtual_machine=vm,
|
||||
type=InterfaceTypeChoices.TYPE_VIRTUAL
|
||||
)
|
||||
|
||||
# If we need anything other than Access, here is were to change it
|
||||
if self.interfaces['nic0']['mode'] == "Access":
|
||||
interface.mode = InterfaceModeChoices.MODE_ACCESS
|
||||
interface.untagged_vlan = prefix.vlan
|
||||
self.interfaces['nic0']['vsphere_port_group'] = prefix.vlan.name
|
||||
|
||||
interface.save()
|
||||
|
||||
# Add interface to IP address
|
||||
ip_address.interface = interface
|
||||
ip_address.save()
|
||||
|
||||
self.appendLogSuccess(log="with interface ", obj=interface).appendLogSuccess(log=", vlan ", obj=prefix.vlan.name)
|
||||
|
||||
return True
|
Loading…
Add table
Add a link
Reference in a new issue