nautobot-app-ssot icon indicating copy to clipboard operation
nautobot-app-ssot copied to clipboard

role__name returns None using contrib

Open rifen opened this issue 1 year ago • 5 comments

Environment

  • Python version: 3.9.18
  • Nautobot version: 2.1.4
  • nautobot-ssot version: 2.2.0

Related Slack: https://networktocode.slack.com/archives/C01NWPK6WHL/p1708560079971619

Expected Behavior

Utilize role__name in my model that lets me add a role to my IPAddresses

Observed Behavior

The role would always return None using a created role or a default role like "Loopback"

Steps to Reproduce

  1. Add role__name to my attributes/data-types of my model using contrib
  2. Using the model with self.ipaddress (from nautobot.ipam.models import IPAddress) in this case. Added role__name="Loopback" for example.
  3. Run my SSOT job and I get back my dict but role is always None

rifen avatar Feb 27 '24 15:02 rifen

Thanks for submitting! Can you provide a full model and adapter to reference?

Kircheneer avatar Feb 27 '24 16:02 Kircheneer

I believe this highlights what you are looking for (I didn't 1:1 copy and paste):

class IPAddressModel(NautobotModel):
    # Metadata about this model
    _model = IPAddress
    _modelname = "ipaddress"
    _identifiers = ("host",)
    _attributes = (
        "mask_length",
        "status__name",
        "role__name",
        "dns_name",
        "description",
        "parent__network",
    )

    # Data type declarations for all identifiers and attributes
    host: str
    mask_length: int
    status__name: str
    role__name: str
    dns_name: str
    description: Optional[str]
    parent__network: str


class NBAdapter(NautobotAdapter):
    ipaddress = IPAddressModel

    top_level = ["ipaddress"]

class Adapter(DiffSync):
    ipaddress = IPAddressModel

    top_level = ["ipaddress"]

    def __init__(self, job: object, sync: object, client: object, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.job = job
        self.client = client
        self.sync = sync

    def load(self):
  
      ip_data = [{
        "host": "10.1.1.2",
        "mask_length": "32",
        "status__name": "Reserved",
        "dns_name": "john.com",
        "description": "Jon Doe",
        "parent__network": "10.1.1.0",
        "role__name": "Loopback", # What it doesn't like
      }]
  
      for data in ip_data:
        ip_addr = self.ipaddress(
            host=data.get("host"),
            mask_length=data.get("mask_length"),
            status__name=data.get("status__name"),
            dns_name=data.get("dns_name"),
            description=data.get("description"),
            parent__network=data.get("parent__network"),
            role__name=data.get("role__name"), # What it doesn't like
        )
        self.add(ip_addr)

rifen avatar Feb 27 '24 18:02 rifen

If you can give me something I can run as is then I can reproduce the issue much quicker, i.e. flesh out

# GETS IP DATA AND ASSIGNS TO VARIABLES

with a list of dictionaries or something like this, and then include the actual operation that shows the faulty behaviour.

Thanks!

Kircheneer avatar Feb 29 '24 10:02 Kircheneer

@Kircheneer I updated my comment from before. Let me know if this is sufficient.

rifen avatar Mar 19 '24 00:03 rifen

This looks good, thanks.

Kircheneer avatar Mar 19 '24 14:03 Kircheneer

@rifen

I've been looking into this and I was able to recreate the described issue. It occurs when an IP Address exists in Nautobot without a role and the SSoT contrib adapter tries to load it. It returns a NoneType object which is not allowed by the DiffSync model you provided so it raises an error.

I also found a config-based fix by wrapping the role__name typing declaration in an Optional[] tag or adding |None to the allowed types (see below). This resolved the issue entirely. If you require the role__name in your source adapter, you can always add a check for it in there.

Since None is a valid value for ipaddress.role, I'm inclined to think this is not a bug and the role__name typing config should reflect that scenario.

@Kircheneer would you still consider this a bug requiring a fix or would one of the below methods be a preferred approach?

class IPAddressModel(NautobotModel):
    # Metadata about this model
    _model = IPAddress
    _modelname = "ipaddress"
    _identifiers = ("host",)
    _attributes = (
        "mask_length",
        "status__name",
        "role__name",
        "dns_name",
        "description",
        "parent__network",
    )

    # Data type declarations for all identifiers and attributes
    host: str
    mask_length: int
    status__name: str
    role__name: Optional[str] 
    dns_name: str
    description: Optional[str]
    parent__network: str

Or you can use:

class IPAddressModel(NautobotModel):
    # Metadata about this model
    _model = IPAddress
    _modelname = "ipaddress"
    _identifiers = ("host",)
    _attributes = (
        "mask_length",
        "status__name",
        "role__name",
        "dns_name",
        "description",
        "parent__network",
    )

    # Data type declarations for all identifiers and attributes
    host: str
    mask_length: int
    status__name: str
    role__name: str|None
    dns_name: str
    description: Optional[str]
    parent__network: str

Renrut5 avatar Oct 09 '24 20:10 Renrut5