Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0e73d82
Add custom script for creating VM
candlerb Nov 19, 2019
6107420
Fixes for v2.7:
candlerb Feb 23, 2020
c3d2dbd
Remove IPAddress.family which was removed from Netbox data model in v…
candlerb Jun 5, 2020
ec13d38
Merge pull request #30 from candlerb/candlerb/29
ryanmerolle Jun 5, 2020
a9ae727
Update create_vm.py (#41)
adriankus Oct 20, 2020
ba1b325
Minor updates to scripts/create_vm.py (#39)
candlerb Oct 20, 2020
74b89f0
Netbox 2.10 preparation (#43)
candlerb Feb 18, 2021
c6152bc
Add object.full_clean() when creating objects
candlerb Jan 11, 2022
ea04503
Merge pull request #54 from candlerb/candlerb/full_clean
ryanmerolle Jan 12, 2022
f53db69
Correct way of assigning tags to objects
candlerb Feb 2, 2022
c763e01
Merge pull request #59 from candlerb/candlerb/tags
ryanmerolle Feb 2, 2022
48f641a
Import create_vm.py script from netbox-community/reports
jrha Jan 19, 2023
ddd1fb7
Always run pylint against all python files
jrha Dec 22, 2022
3bb80ca
Disable irrelevant pylint warnings
jrha Jan 19, 2023
5e5a95d
Remove commented out lines
jrha Jan 19, 2023
de94d50
Fix long import line
jrha Jan 19, 2023
28f0382
Remove unused import
jrha Jan 19, 2023
a403c4d
Fix reference to incorrect variable
jrha Jan 19, 2023
cf164c2
pylint: Ignore unused argument to run()
jrha Jan 20, 2023
91be9fd
Use PEP-8 style name for virtual_machine
jrha Jan 19, 2023
d4b866b
Use PEP-8 style naming for vm_interface
jrha Jan 19, 2023
99dc6b6
Use PEP-8 style naming for ip_address
jrha Jan 19, 2023
2e26ff2
Allow the field configuration to be customised
jrha Jan 20, 2023
a45f882
Allow custom fields to be specified in YAML file
jrha Jan 20, 2023
99cc123
Import any types used by custom fields not already imported
jrha Jan 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ jobs:
pip install -r requirements.txt
- name: Analysing any changed code with pylint
run: |
git diff --name-only HEAD^ | grep '^aquilon/' | xargs -r pylint --max-line-length=120
find . -type f -name '*.py' | xargs -r pylint --max-line-length=120
167 changes: 167 additions & 0 deletions scripts/create_vm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
"""
This script allows you to create a VM, an interface and primary IP address
all in one screen.

Workaround for issues:
https://github.com/netbox-community/netbox/issues/1492
https://github.com/netbox-community/netbox/issues/648
"""

# pylint: disable=missing-function-docstring,too-few-public-methods,import-error,missing-class-docstring

from dcim.models import DeviceRole, Platform
from django.core.exceptions import ObjectDoesNotExist
from extras.models import Tag
from extras.scripts import Script, StringVar, IPAddressWithMaskVar, ObjectVar
from extras.scripts import MultiObjectVar, ChoiceVar, IntegerVar, TextVar
from ipam.choices import IPAddressStatusChoices
from ipam.models import IPAddress
from tenancy.models import Tenant
from virtualization.choices import VirtualMachineStatusChoices
from virtualization.models import Cluster, VirtualMachine, VMInterface


field_config = {
'dns_name': {'required': False},
'vm_tags': {'required': False},
'primary_ip4': {'required': False},
'primary_ip6': {'required': False},
'role': {'required': False},
'tenant': {'required': False},
'platform': {'required': False},
'interface_name': {'default': 'eth0'},
'mac_address': {'required': False},
'vcpus': {'required': False},
'memory': {'required': False},
'disk': {'required': False},
'comments': {'required': False},
'custom_fields': {},
}

# Allow the field configuration to be customised by a site specific YAML file
# For example:
# ---
# dns_name:
# required: True
#
# interface_name:
# regex: '^eth[0-9]+$'
# ---
#
# Custom fields can also be specified, type will default to String if not specified.
# Any other valid arguments to the constructor can be provided.
# For example:
# ---
# custom_fields:
# vmid:
# label: VM ID
# required: True
# regex: ^vm-[0-9]+$
# ---
try:
field_config_custom = Script().load_yaml('create_vm.yaml')
if isinstance(field_config_custom, dict):
# Merge field configuration, but don't allow arbitrary fields to be added to the YAML file
for field_name, field_properties in field_config.items():
if field_name in field_config_custom:
field_properties.update(field_config_custom[field_name])
except FileNotFoundError:
pass


class NewVM(Script):
class Meta:
name = "New VM"
description = "Create a new VM"

vm_name = StringVar(label="VM name")
dns_name = StringVar(label="DNS name", **field_config['dns_name'])
vm_tags = MultiObjectVar(model=Tag, label="VM tags", **field_config['vm_tags'])
primary_ip4 = IPAddressWithMaskVar(label="IPv4 address", **field_config['primary_ip4'])
primary_ip6 = IPAddressWithMaskVar(label="IPv6 address", **field_config['primary_ip6'])
role = ObjectVar(model=DeviceRole, query_params=dict(vm_role=True), **field_config['role'])
status = ChoiceVar(VirtualMachineStatusChoices, default=VirtualMachineStatusChoices.STATUS_ACTIVE)
cluster = ObjectVar(model=Cluster)
tenant = ObjectVar(model=Tenant, **field_config['tenant'])
platform = ObjectVar(model=Platform, **field_config['platform'])
interface_name = StringVar(**field_config['interface_name'])
mac_address = StringVar(label="MAC address", **field_config['mac_address'])
vcpus = IntegerVar(label="VCPUs", **field_config['vcpus'])
memory = IntegerVar(label="Memory (MB)", **field_config['memory'])
disk = IntegerVar(label="Disk (GB)", **field_config['disk'])

# Add custom fields
custom_fields = {}
for cf_name, cf_properties in field_config['custom_fields'].items():
cls = 'StringVar'
if 'type' in cf_properties:
cls = cf_properties['type'].title() + 'Var'
del cf_properties['type']

# Import any missing types
if cls not in globals():
globals()[cls] = getattr(__import__('extras.scripts', fromlist=['extras']), cls)

vars()[f'cf_{cf_name}'] = globals()[cls](**cf_properties)

comments = TextVar(label="Comments", **field_config['comments'])

def run(self, data, commit): # pylint: disable=unused-argument
virtual_machine = VirtualMachine(
name=data["vm_name"],
role=data["role"],
status=data["status"],
cluster=data["cluster"],
platform=data["platform"],
vcpus=data["vcpus"],
memory=data["memory"],
disk=data["disk"],
comments=data["comments"],
tenant=data.get("tenant"),
)
virtual_machine.full_clean()
virtual_machine.save()
virtual_machine.tags.set(data["vm_tags"])

for cf_name in field_config['custom_fields']:
virtual_machine.custom_field_data[cf_name] = data[f'cf_{cf_name}']

vm_interface = VMInterface(
name=data["interface_name"],
mac_address=data["mac_address"],
virtual_machine=virtual_machine,
)
vm_interface.full_clean()
vm_interface.save()

def add_addr(addr, family):
if not addr:
return
if addr.version != family:
raise RuntimeError(f"Wrong family for {addr}")
try:
ip_address = IPAddress.objects.get(
address=addr,
)
result = "Assigned"
except ObjectDoesNotExist:
ip_address = IPAddress(
address=addr,
)
result = "Created"
ip_address.status = IPAddressStatusChoices.STATUS_ACTIVE
ip_address.dns_name = data["dns_name"]
if ip_address.assigned_object:
raise RuntimeError(f"Address {addr} is already assigned")
ip_address.assigned_object = vm_interface
ip_address.tenant = data.get("tenant")
ip_address.full_clean()
ip_address.save()
self.log_info(f"{result} IP address {ip_address.address} {ip_address.vrf or ''}")
setattr(virtual_machine, f"primary_ip{family}", ip_address)

add_addr(data["primary_ip4"], 4)
add_addr(data["primary_ip6"], 6)
virtual_machine.full_clean()
virtual_machine.save()
self.log_success(f"Created VM [{virtual_machine.name}](/virtualization/virtual-machines/{virtual_machine.id}/)")