Generators and Services in Infrahub – Part 3

In this part of our series on generators and services, we’re diving into the practical aspects of setting up a generator. In our last video, we covered the planning phase, including identifying use cases, defining deliverables, and mapping workflows. This time, we’ll focus on the actual coding and configuration needed in InfraHub to get our generator up and running.

Setting the Generator Up

To start, we need to define our generator within the .infrahub.yaml file. This is crucial as it outlines how the different components come together. Our definition file will specify the locations of the Python code and the GraphQL query required to pull the data we need for our generator to function.

Walking through our generator definition:

  • name: We set a name for our generator, like site_generator.
  • file_path: This points to where our generator code resides.
  • targets: We define our targets, which will be set up in a future video.
  • query: We specify the query name, which is used further in the file, and the path to the query file.
  • class_name: We define a name for the class to use in the generator
 
And then a similar setup for the query:
 
  • name: The name we use to point at the query
  • file_path: This points to where our query file resides.
				
					---
generator_definitions:
  - name: site_generator
    file_path: "generators/site_generator.py"
    targets: "generator_sites"
    query: "site_details"
    class_name: "SiteGenerator"

queries:
  - name: site_details
    file_path: "queries/site_details.gql"
				
			

GraphQL Query

The GraphQL query will filter out the necessary information for our generator. We will focus on returning the following variable values for our site:

  • edges > node > name: This will return the name of the site we are targeting.
  • edges > node > site_prefix: This return the site prefix for device and interface names.
  • edges > node > homing: Whether the site is single or dual-homed.
				
					query site_details($site_name: String!) {
  LocationSite(name__value: $site_name) {
    edges {
      node {
        name {
          value
        }
        site_prefix {
          value
        }
        homing {
          value
        }
      }
    }
  }
}
				
			

Generator File

This is where the magic happens.  The generator is coded with Python and uses the Infrahub SDK to bring the logic to life.  I’ll use comments within the Python code to walk through the flow:

				
					import logging
from infrahub_sdk.generator import InfrahubGenerator

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Suppress httpx logs for ease of viewing on the console
httpx_logger = logging.getLogger("httpx")
httpx_logger.setLevel(logging.WARNING)

# Call our SiteGenerator as defined in our .infrahub.yml file
class SiteGenerator(InfrahubGenerator):

##### Receive the output of the GraphQL query
    async def generate(self, data: dict) -> None:
        logger.info("Received data for processing.")

        if not data["LocationSite"]["edges"]:
            logger.warning("No sites found in query result.")
            return

######### Extract site details we need to build our devices
        site = data["LocationSite"]["edges"][0]["node"]
        site_name = site["name"]["value"]
        site_prefix = site["site_prefix"]["value"]
        homing = site["homing"]["value"]

        logger.info(f"Processing site: {site_name} with prefix: {site_prefix}, Homing: {homing}")

######### Fetch the Management IP Pool
        management_pool = await self.client.get(
            kind="CoreIPAddressPool",
            any__value="Management Pool",
            raise_when_missing=False
        )

        if not management_pool:
            logger.error("Management Pool not found. Ensure it is created in the GUI.")
            return

        logger.info(f"Using IPAM Pool: {management_pool}")

######### Define device configuration based on homing type
        device_config = {
            "single_homed": [
                {"name": f"{site_prefix}-router-01", "role": "core", "type": "router"},
                {"name": f"{site_prefix}-firewall-01", "role": "firewall", "type": "firewall"},
                {"name": f"{site_prefix}-switch-01", "role": "end_user_switch", "type": "switch"},
            ],
            "dual_homed": [
                {"name": f"{site_prefix}-router-01", "role": "core", "type": "router"},
                {"name": f"{site_prefix}-router-02", "role": "core", "type": "router"},
                {"name": f"{site_prefix}-firewall-01", "role": "firewall", "type": "firewall"},
                {"name": f"{site_prefix}-firewall-02", "role": "firewall", "type": "firewall"},
                {"name": f"{site_prefix}-switch-01", "role": "end_user_switch", "type": "switch"},
            ],
        }

        devices = device_config.get(homing, [])

######### Begin creating the objects for our service of a new site
        for device in devices:
            device_name = device["name"]
            device_role = device["role"]
            device_type = device["type"]

############# Create device
            logger.info(f"Creating device: {device_name} ({device_role})")

            device_obj = await self.client.create(
                kind="InfraDevice",
                data={
                    "name": device_name,
                    "role": device_role,
                    "type": device_type,
                    "status": "active",
                    "site": site_name,
                },
            )
            await device_obj.save(allow_upsert=True)

############# Create interfaces for device
            for iface_num in range(1, 3):
                interface_name = f"{device_name}-iface{iface_num}"
                logger.info(f"Adding interface: {interface_name}")

                interface = await self.client.create(
                    kind="InfraInterfaceL3",
                    data={
                        "name": interface_name,
                        "device": device_obj,
                        "speed": 1000,
                        "status": "active",
                        "role": "management" if iface_num == 1 else "uplink",
                    },
                )
                await interface.save(allow_upsert=True)

############# Obtain and associate IP address for management interfaces
                ip_address = await self.client.allocate_next_ip_address(
                    resource_pool=management_pool,
                    identifier=f"{interface_name}-ip",
                    data={"description": f"IP for {interface_name}"}
                )
                ip_address.interface = interface
                await ip_address.save(allow_upsert=True)

                logger.info(f"Allocated IP: {ip_address.address.value} and linked to {interface_name}")

        logger.info("Device and interface creation completed.")

				
			

Let's Run It!

Here we can see the output from the console of running the generator for site Chicago

infrahubctl generator --branch "generator-test" site_generator site_name=Chicago

And now we can see our devices from inside the Infrahub UI

kFJnWkFedyAAAAAElFTkSuQmCC

With the generator functioning as expected, we now look towards operationalizing it. This means integrating the generator into the application for easier consumption by users.

 

Conclusion

With our next installment in the series, we will implement a way to control when generators run and against which sites. This will allow for more flexibility and prevent unnecessary executions against all sites.

We will also be bringing the generator, and all of the required files, into a git repository so we can operationalize its usage.  We’ll also look at how to leverage our proposed changes feature to help teams even more.

To learn more about Generators today, check out the following links to our documentation:

 

Share the Post:

JOIN OUR MAILING LIST

Please enter your email address to stay informed about OpsMill developments. Your email address will be stored according to GDPR and will never be sold.

REQUEST A DEMO

See OpsMill in action and learn how it can help you achieve your goals. Fill out the form below to schedule a personalized demo.

By submitting this form, you agree that your personal data will be stored and processed by OpsMill in accordance with our privacy policy.