netbox-community / migration-scripts

Apache License 2.0
11 stars 4 forks source link

Not able to run script #2

Open stoms12 opened 2 years ago

stoms12 commented 2 years ago

We are having an issue with running these scripts: Running Netbox version 3.1.11-ldap-1.6.1 in docker

(venv) bash-5.1$ python3 manage.py runscript --loglevel debug --data '{"asn_rir": 2, "clear_site_field": false}' netbox_v32_migration.MigrateSiteASNsScript
🧬 loaded config '/etc/netbox/config/configuration.py'
🧬 loaded config '/etc/netbox/config/extra.py'
🧬 loaded config '/etc/netbox/config/logging.py'
🧬 loaded config '/etc/netbox/config/plugins.py'

[2022-08-25 09:27:08,332][INFO] - Running script (commit=False)
[2022-08-25 09:27:08,334][INFO] - Found 2 sites with a legacy ASN defined.
[2022-08-25 09:27:08,336][INFO] - Creating new ASN: 16509
[2022-08-25 09:27:08,337][ERROR] - An exception occurred: `ValueError: Cannot assign "2": "ASN.rir" must be a "RIR" instance.`
---
Traceback (most recent call last):
  File "/opt/netbox/netbox/extras/management/commands/runscript.py", line 44, in _run_script
    script.output = script.run(data=data, commit=commit)
  File "/etc/netbox/scripts/netbox_v32_migration.py", line 150, in run
    asn = ASN(asn=site.asn, rir=data['asn_rir'])
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/db/models/base.py", line 485, in __init__
    _setattr(self, field.name, rel_obj)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/db/models/fields/related_descriptors.py", line 215, in __set__
    raise ValueError(
ValueError: Cannot assign "2": "ASN.rir" must be a "RIR" instance.

---
[2022-08-25 09:27:08,337][INFO] - Database changes have been reverted due to error.
[2022-08-25 09:27:08,337][ERROR] - Exception raised during script execution: Cannot assign "2": "ASN.rir" must be a "RIR" instance.
[2022-08-25 09:27:08,340][INFO] - Script completed in 0 minutes, 0.01 seconds

Also seeing the same issue with the contact migration script

(venv) bash-5.1$ python3 manage.py runscript --loglevel debug --data '{"contact_role": 1, "contact_priority": "primary", "clear_site_fields": false}' netbox_v32_migration.MigrateSiteContactsScript
🧬 loaded config '/etc/netbox/config/configuration.py'
🧬 loaded config '/etc/netbox/config/extra.py'
🧬 loaded config '/etc/netbox/config/logging.py'
🧬 loaded config '/etc/netbox/config/plugins.py'
[2022-08-25 09:31:59,190][INFO] - Running script (commit=False)
[2022-08-25 09:31:59,193][INFO] - Found 9 sites with legacy contact information defined.
[2022-08-25 09:31:59,195][INFO] - Creating new contact: John Smith
[2022-08-25 09:31:59,292][INFO] - Assigning contact John Smith to site Johns House
[2022-08-25 09:31:59,294][ERROR] - An exception occurred: `ValueError: Cannot assign "1": "ContactAssignment.role" must be a "ContactRole" instance.`
---
Traceback (most recent call last):
  File "/opt/netbox/netbox/extras/management/commands/runscript.py", line 44, in _run_script
    script.output = script.run(data=data, commit=commit)
  File "/etc/netbox/scripts/netbox_v32_migration.py", line 87, in run
    assignment = ContactAssignment(
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/db/models/base.py", line 485, in __init__
    _setattr(self, field.name, rel_obj)
  File "/opt/netbox/venv/lib/python3.9/site-packages/django/db/models/fields/related_descriptors.py", line 215, in __set__
    raise ValueError(
ValueError: Cannot assign "1": "ContactAssignment.role" must be a "ContactRole" instance.

---
[2022-08-25 09:31:59,294][INFO] - Database changes have been reverted due to error.
[2022-08-25 09:31:59,294][ERROR] - Exception raised during script execution: Cannot assign "1": "ContactAssignment.role" must be a "ContactRole" instance.
[2022-08-25 09:31:59,298][INFO] - Script completed in 0 minutes, 0.11 seconds
AzyCrw4282 commented 1 year ago

did you find a solution for this?

stoms12 commented 1 year ago

I created a temporary Contact Role to assign migrated contacts to under Organisation -> Contact Roles I also had to create a temporary ASN RIR to assign migrated ASNs to under IPAM -> RIRs

Then made the following changes to the script

from dcim.models import Site
from extras.scripts import BooleanVar, ChoiceVar, ObjectVar, Script
from ipam.models import ASN, RIR
from tenancy.choices import ContactPriorityChoices
from tenancy.models import Contact, ContactAssignment, ContactRole

name = "NetBox v3.2 Migration"

class MigrateSiteContactsScript(Script):
    """
    This script looks for Sites with a contact_name defined and attempts to create new Contact instances from the
    associated contact data. A new Contact will be created for each *unique* set of name, phone, and email values.
    Contacts for which an assignment already exists will be skipped.

    While suitable for many use cases, the author is encouraged to modify this script to better suit his or her
    own environment as needed.
    """
    contact_role = ObjectVar(
        model=ContactRole,
        description="The role to apply when assigning contacts to sites"
    )
    contact_priority = ChoiceVar(
        choices=ContactPriorityChoices,
        required=False,
        description="The priority to apply when assigning contacts to sites"
    )
    clear_site_fields = BooleanVar(
        required=False,
        description="Clear legacy site contact values after creating a new contact assignment"
    )

    class Meta:
        name = "Migrate site contacts"
        description = "Create new contact objects from legacy site contact fields"
        commit_default = True

    @staticmethod
    def _get_contact_data(site):
        name = site.contact_name.strip()
        phone = site.contact_phone.strip()
        email = site.contact_email.strip()

        # Return only fields which have a value after sanitization
        attrs = {
            'name': name
        }
        if phone:
            attrs['phone'] = phone
        if email:
            attrs['email'] = email

        return attrs

    def run(self, data, commit=Meta.commit_default):

        contacts_created = 0
        assignments_created = 0

        # Retrieve all Sites with contact_name defined
        sites = Site.objects.exclude(contact_name='')
        if not sites:
            self.log_warning(f"No sites found with legacy contact information defined; aborting.")
            return
        self.log_info(f"Found {sites.count()} sites with legacy contact information defined.")
        contact_role_object = ContactRole.objects.filter(id=data['contact_role'])[0]
        for site in sites:

            # Extract the contact attributes from the Site
            contact_data = self._get_contact_data(site)
            contact = Contact.objects.filter(**contact_data).first()

            # Create a new Contact if this combination of attributes is new
            if not contact:
                self.log_success(f"Creating new contact: {contact_data['name']}")
                contact = Contact(**contact_data)
                contact.save()
                contacts_created += 1

            # Check whether a ContactAssignment already exists for this Site and Contact
            elif site.contacts.filter(contact=contact).exists():
                self.log_info(f"Skipping contact {contact} for site {site}; assignment already exists")
                continue

            # Assign the Contact to the Site
            self.log_success(f"Assigning contact {contact} to site {site}")
            assignment = ContactAssignment(
                object=site,
                contact=contact,
                role=contact_role_object,
                priority=data['contact_priority']
            )
            assignment.save()
            assignments_created += 1

            # Clear legacy contact fields on Site (if enabled)
            if data['clear_site_fields']:
                self.log_debug(f"Clearing legacy contact data for site {site}")
                Site.objects.filter(pk=site.pk).update(
                    contact_name='',
                    contact_phone='',
                    contact_email=''
                )

        self.log_success(f"Created {contacts_created} contacts")
        self.log_success(f"Created {assignments_created} contact assignments")

class MigrateSiteASNsScript(Script):
    """
    This script looks for Sites which have a legacy ASN set, and creates or assigns an ASN object in its place.

    While suitable for many use cases, the author is encouraged to modify this script to better suit his or her
    own environment as needed.
    """
    asn_rir = ObjectVar(
        model=RIR,
        description="RIR to assign to newly created ASNs",
        label='RIR'
    )
    clear_site_field = BooleanVar(
        required=False,
        description="Clear legacy site ASN field after migration"
    )

    class Meta:
        name = "Migrate site ASNs"
        description = "Create/assign ASN objects from legacy site ASN fields"
        commit_default = False

    def run(self, data, commit):

        asns_created = 0
        asns_assigned = 0

        # Find all Sites with a legacy ASN value assigned
        sites = Site.objects.filter(asn__isnull=False)
        if not sites:
            self.log_warning(f"No sites found with a legacy ASN defined; aborting.")
            return
        self.log_info(f"Found {sites.count()} sites with a legacy ASN defined.")
        asn_rir_object = RIR.objects.filter(id=data['asn_rir'])[0]
        for site in sites:

            asn = ASN.objects.filter(asn=site.asn).first()

            # Create a new ASN object if this AS number is new
            if not asn:
                self.log_success(f"Creating new ASN: {site.asn}")
                asn = ASN(asn=site.asn, rir=asn_rir_object)
                asn.save()
                asns_created += 1

            # Check whether this ASN has already been assigned to this Site (via the many-to-many relationship)
            elif asn in site.asns.all():
                self.log_info(f"Skipping ASN {asn} for site {site}; already assigned")
                continue

            # Assign the ASN to the Site
            self.log_success(f"Assigning ASN {asn} to site {site}")
            site.asns.add(asn)
            asns_assigned += 1

            # Clear legacy ASN field on Site (if enabled)
            if data['clear_site_field']:
                self.log_debug(f"Clearing legacy ASN field for site {site}")
                Site.objects.filter(pk=site.pk).update(asn=None)

        self.log_success(f"Created {asns_created} ASNs")
        self.log_success(f"Assigned {asns_assigned} ASNs to sites")
cynici commented 1 year ago

Hi @AzyCrw4282 I have run the script successfully without any issue but the NetBox container must use exactly this release tag 1.6.0 and not 1.6.1 (OP's mistake)

https://github.com/netbox-community/netbox-docker/tree/1.6.0

which corresponds to the docker image netboxcommunity/netbox:v3.1.11-ldap-1.6.0