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
|
- netbox-nginx-config:/etc/netbox-nginx/:ro
|
||||||
- /etc/cert-client:/etc/cert-client:ro
|
- /etc/cert-client:/etc/cert-client:ro
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:11-alpine
|
image: postgres:10.4-alpine
|
||||||
env_file: env/postgres.env
|
env_file: env/postgres.env
|
||||||
volumes:
|
volumes:
|
||||||
- netbox-postgres-data:/var/lib/postgresql/data
|
- 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