[{"content":"","date":null,"permalink":"https://josh-v.com/categories/ai/","section":"Categories","summary":"","title":"Ai"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/ai/","section":"Tags","summary":"","title":"Ai"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/automation/","section":"Categories","summary":"","title":"Automation"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/","section":"Categories","summary":"","title":"Categories"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/dictation/","section":"Tags","summary":"","title":"Dictation"},{"content":"I\u0026rsquo;ve been using the app FluidVoice now for about a week, and I\u0026rsquo;m really liking it. It\u0026rsquo;s a capable voice transcription tool with a layer of AI on top. It\u0026rsquo;s free, open source (GPLv3), and made by altic-dev, with everything running on-device by default. What sets it apart for me is that it\u0026rsquo;s a local, private alternative to tools like Wispr Flow — nothing has to leave my Mac for it to work. Some of the features that I like using the most include:\nHold-to-talk transcription from a single hotkey A live view of what\u0026rsquo;s being transcribed A wide choice of transcription models The option to switch from hold-to-talk to press-to-start/stop for long dictation sessions Getting FluidVoice FluidVoice runs on macOS 15 (Sequoia) or later — Apple Silicon natively, and Intel Macs via the Whisper models. The fastest way to install is with Homebrew:\nbrew install --cask fluidvoice You can also grab the latest build from the releases page. iOS and Windows are on a waitlist, with Linux on the way.\nHow I Use It #Most of my day comes down to getting words out somewhere — blog posts, documentation, pull request descriptions, chat messages, and more and more, prompts to AI tools. FluidVoice has quietly slotted into all of it. I hold the hotkey, say what I mean, and the text lands wherever my cursor already is, whether that\u0026rsquo;s my editor, a browser text box, or a terminal. There\u0026rsquo;s no separate window to copy out of and no context switch.\nSo far, plain voice-to-text has been my primary use. Talking through a thought is faster than typing it, and I find I give more detail when I\u0026rsquo;m speaking, because there\u0026rsquo;s less friction between the thought and getting it down — a rambling, detailed prompt that I\u0026rsquo;d never bother to type is suddenly cheap to produce. For longer stretches, switching from hold-to-talk to press-to-start-and-stop lets me dictate a few paragraphs at a time without keeping a key held down.\nI\u0026rsquo;m still early with it, though. There\u0026rsquo;s a lot more here I want to dig into — the AI enhancement and file transcription especially — and from what I\u0026rsquo;ve seen so far, it\u0026rsquo;s a fit for how I work and pointed in the right direction for where voice-to-text is heading.\nVoice Transcription #Voice transcription really isn\u0026rsquo;t new. It\u0026rsquo;s been around for quite some time. It\u0026rsquo;s just now lately with how AI has been progressing and some of the videos about getting prompts to AI, that I believe that voice transcription is really taking off.\nOne of the things I like most is the range of speech models you can pick from, so you can trade off speed against accuracy for how you work. The options include Nemotron Speech 3.5, Parakeet Flash and TDT (v3 and v2), Cohere Transcribe, Apple Speech, and Whisper in several sizes. They all run on-device, so nothing leaves the machine to get your words transcribed.\nCustom Dictionary #One feature that has quickly become essential for me is the custom dictionary. Out of the box, a speech model has no idea what to do with a word like Nautobot — it comes out as \u0026ldquo;not a bot,\u0026rdquo; \u0026ldquo;now to bot,\u0026rdquo; or something else entirely every time. With the custom dictionary I can add the term once and have it transcribed correctly from then on. The same goes for the rest of the vocabulary I use every day: product names, company names, and acronyms that no general-purpose model was ever trained on.\nIt also learns as you go. When you keep correcting the same misheard word, FluidVoice can notice the pattern and offer to save the correction as a dictionary entry, so your personal vocabulary gets more accurate the more you use it. For anyone writing in a specialized space — networking, in my case — this is the difference between transcription that\u0026rsquo;s usable and transcription you spend more time fixing than you saved.\nAI Enhancement #I have not yet played with the AI enhancement, but there are a number of providers to choose from. When I looked, the options included Fluid Intelligence (the local, on-device model) itself, OpenAI, Anthropic, and Ollama and LM Studio running locally — though the exact set has been shifting release to release, so check the app for what\u0026rsquo;s current. And if none of those suit your needs, there\u0026rsquo;s the ability to point at your own AI provider with a custom URL, since it uses OpenAI-compatible endpoints.\nFile Transcription #One of the more interesting things that I\u0026rsquo;m going to be looking forward to is using the meeting transcription feature where you can upload your video files or audio files to get text output.\nDashboard #There\u0026rsquo;s also a nice dashboard that tracks your usage — words transcribed, minutes and time saved, and session counts, both for the day and overall. The copy of the dashboard below is from a fresh install on a newer machine, so the numbers are still near zero.\nSummary #At this point, FluidVoice is going to be my go-to for my voice transcription. What I plan to get out of voice transcription is the ability to get text down quicker, work with AI a bit quicker, amongst other uses that I don\u0026rsquo;t know what will come from using the tech.\nLinks # FluidVoice website FluidVoice on GitHub — source, releases, and issues Community Discord ","date":"2026-07-18","permalink":"https://josh-v.com/fluid_voice_start/","section":"Posts","summary":"A week in with FluidVoice, a push-to-talk dictation app that turns speech into text wherever your cursor is. A look at the features I lean on most — hold-to-transcribe, live transcription feedback, a wide choice of models, and toggling between hold and press-to-start for longer sessions — plus the AI enhancement, file transcription, and dashboard I\u0026rsquo;m still exploring.","title":"FluidVoice - My Dictation Assistant"},{"content":"","date":null,"permalink":"https://josh-v.com/","section":"Josh VanDeraa","summary":"","title":"Josh VanDeraa"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/macos/","section":"Tags","summary":"","title":"Macos"},{"content":"","date":null,"permalink":"https://josh-v.com/posts/","section":"Posts","summary":"","title":"Posts"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/productivity/","section":"Tags","summary":"","title":"Productivity"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/","section":"Tags","summary":"","title":"Tags"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/transcription/","section":"Tags","summary":"","title":"Transcription"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/voice/","section":"Categories","summary":"","title":"Voice"},{"content":"Books #For several years I have been providing content in blog posts. This has evolved into writing books as well.\nOpen Source Network Management, 2nd Edition # The Second Edition is out now — an improvement and modernization of the first. Across thirteen hands-on chapters it is a guide to building a complete, modern network management platform — source of truth to full observability — entirely from open source, on your own infrastructure, all in a real Containerlab lab you can follow along with end to end.\nHead to osnm.josh-v.com for the full details, formats, and a free sample chapter.\nNetwork Automation with Nautobot # I\u0026rsquo;m proud to have been an author in the Network Automation with Nautobot found on Amazon book that has recently been released. The book covers:\nLearn how documenting your network in Nautobot can accelerate your network automation journey Apply NetDevOps to your network by leveraging Nautobot as a network source of truth Minimize tool sprawl by extending, using, or building Nautobot Apps Nautobot enables network teams to build a scalable and extensible network source of truth that provides a foundation to power any network automation stack.\nWith the help of this guide, you\u0026rsquo;ll learn how to deploy, manage, and integrate Nautobot as a source of truth and network automation platform. As you progress, you\u0026rsquo;ll learn what a network source of truth is, the relationship between data and network automation, and network data models. You\u0026rsquo;ll also gain a broad understanding of Nautobot and its robust features that allow maximum flexibility. A dedicated section will show you how to construct a single source of truth with Nautobot and help you explore its programmatic APIs, including REST APIs, GraphQL, webhooks, and Nautobot Job Hooks. Later, you\u0026rsquo;ll develop custom data models and custom apps for Nautobot and experience its extensibility and powerful developer API. In the last part of this book, you\u0026rsquo;ll discover how to deploy configuration compliance and automated remediation once Nautobot is deployed as a network source of truth.\nBy the end of this book, you\u0026rsquo;ll be able to design and manage Nautobot as a network source of truth, understand its key features, and extend Nautobot by creating custom data models and apps that suit your network and your team.\nModern Network Observability #The second book in 2024 is Modern Network Observability, which provides a hands-on approach to using open source tools for network observability in 2024. (More details coming soon)\n-Josh\n","date":"2026-07-17","permalink":"https://josh-v.com/book/","section":"Josh VanDeraa","summary":"\u003ch1 id=\"books\" class=\"relative group\"\u003eBooks \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#books\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h1\u003e\u003cp\u003eFor several years I have been providing content in blog posts. This has evolved into writing books as well.\u003c/p\u003e\n\u003ch2 id=\"open-source-network-management-2nd-edition\" class=\"relative group\"\u003eOpen Source Network Management, 2nd Edition \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#open-source-network-management-2nd-edition\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cimg src=\"/images/osnm2-cover.png\" alt=\"Open Source Network Management, 2nd Edition Cover\" class=\"float-right ml-4 mb-4 w-1/3 rounded-lg shadow-lg\"\u003e\n\u003cp\u003eThe \u003cstrong\u003eSecond Edition\u003c/strong\u003e is out now — an improvement and modernization of the first. Across thirteen hands-on chapters it is a guide to building a complete, modern network management platform — source of truth to full observability — entirely from open source, on your own infrastructure, all in a real \u003ca href=\"https://containerlab.dev/\" target=\"_blank\" rel=\"noreferrer\"\u003eContainerlab\u003c/a\u003e lab you can follow along with end to end.\u003c/p\u003e","title":"Books"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/book/","section":"Categories","summary":"","title":"Book"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/book/","section":"Tags","summary":"","title":"Book"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/network/","section":"Tags","summary":"","title":"Network"},{"content":"Today (July 11, 2026) I\u0026rsquo;m announcing the availability of the second edition of my book - Open Source Network Management. This is an updated version on the writing that I put together several years ago about how to manage networks using Open Source tooling. I wanted to take the time to get an update out and make a few general updates as well to the guide. On top of the initial writing, I\u0026rsquo;m also working on alternative chapters or a take on a \u0026ldquo;Choose Your Own Adventure\u0026rdquo; books that I used to love as a kid. I\u0026rsquo;m working on putting together additional chapters with tooling that I think Enterprises may want to adopt in order to continue managing networks in a new, modern way.\nUpdates Made #First, it is updated to 2026 versions of all of the software. That was the first thing that was definitely a requirement. The tooling has come a long ways and continued to be adopted. Nautobot has been updated from version 1.x to 3.x now. Which has many awesome improvements along the way over the years. And it is even better now.\nThe one glaring hole from the first edition was configuration management. At that time I didn\u0026rsquo;t think Golden Config from Nautobot was the right tool to make changes to network devices. That has now changed and I have added Nautobot Golden Config to the book.\nMetrics \u0026amp; Logs #I\u0026rsquo;ve gone ahead and brought in Victoria Metrics and Victoria Logs to the chapters on Metrics and Logs. These are drop in replacements and there are additional options that will be brought in with the additional chapters that will be made available.\nRemaining Consistent #A lot of the rest is remaining consistent, but there are new capabilities within the tools. Including improved Alerting and Dashboarding with Grafana and updates to the rest of the stack involved.\nSummary #You can get your copy now, it is live at https://osnm.josh-v.com. The digital versions are available now and the bundle of digital + physical print is available for pre-order, with the digital versions being delivered immediately. Let me know your thoughts on the book below.\n-Josh\n","date":"2026-07-11","permalink":"https://josh-v.com/osnm2/","section":"Posts","summary":"Announcing today, the availability of the second edition of my book Open Source Network Management. The book is now available first only at \u003ca href=\"https://osnm.josh-v.com\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://osnm.josh-v.com\u003c/a\u003e for digital purchase. You can pre-order for the physical book + digital bundle today and get the both. Physical books are expected in August.","title":"Open Source Network Management Second Edition"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/open-source/","section":"Tags","summary":"","title":"Open-Source"},{"content":"For a long while I was getting it all wrong with my setup of AI local inference. I thought that for each sub-agent that I would spin up on my OpenClaw, the system would use that amount of VRAM. I\u0026rsquo;ve been running Qwen3.5-122B-A10B at 6-bit quantization. This model uses 101GB when loading the weights. So I thought if I had two sub-agents using this model, I would need 202GB of RAM. I was wrong.\nSo what I was doing was loading multiple models into my system and pointing different sub-agents at different models. My heavy coding agent would use a much lighter model than the one I was using for reasoning and research.\nIntroduction to Weights #I finally went ahead and asked an AI system (I forget which one - Harry, Gemini, ChatGPT, or Claude). I asked the simple question: do I need to have multiple models running for each sub-agent to work simultaneously? The quick answer was no. I then learned that the bulk of the memory numbers you see quoted are for AI Model Weights - the trained parameters that get loaded into memory and used to respond to prompts.\nThe weights are loaded once into memory. On top of that, each active request gets a KV-cache - a working memory that stores the computed attention keys and values for the current context. The KV-cache is dynamic: it grows with each token processed and is allocated per-slot, not per-model. So running 4 agents against one model means one copy of weights + 4 small KV-caches, not 4 copies of the model.\nHere is what is actually happening on the system:\nSummary #The technology road is quite the journey. This might be a \u0026ldquo;yeah, you didn\u0026rsquo;t know that?\u0026rdquo; moment for some, but I didn\u0026rsquo;t, and I\u0026rsquo;ve learned. Since consolidating all my sub-agents onto the same local model, the performance across the board has improved significantly - even for the agents that were previously on smaller models. The system is more stable, and I\u0026rsquo;m getting better results.\nIf I had this misunderstanding, there\u0026rsquo;s a non-zero chance someone else does too. Hope this helps.\n-Josh\n","date":"2026-03-28","permalink":"https://josh-v.com/local-inference-memory/","section":"Posts","summary":"I thought running multiple AI agents locally meant loading the model into memory once per agent. Turns out model weights are shared - only the KV-cache scales per request. Here\u0026rsquo;s what I learned and how consolidating to a single model improved everything.","title":"AI: Local Models Memory"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/local_ai/","section":"Tags","summary":"","title":"Local_ai"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/openclaw/","section":"Tags","summary":"","title":"Openclaw"},{"content":"Earlier in the day today I took notice that my RSS feeds were not working, most likely from when I made the migration from Material for MkDocs blog post over to the Hugo site. I\u0026rsquo;m still not regretting making the change. But I needed to take care of the issue. So instead of taking the time to check into what my RSS feed set up was, I wanted to just have the issue taken care of. So naturally I turned my attention over to Harry to help take care of this.\nIssue Identified - Too Many URLs #Over the years I have had two different feeds show up from my blogs, so a little bit of technical debt for sure. And I\u0026rsquo;m going to keep that debt in place most likely. It doesn\u0026rsquo;t hurt too bad at this point. As I checked out my Feedly setup I saw that there were 2 URLs for the feeds and they both were not being found. So I copied and pasted the URLs from the Feedly URLs and asked Harry to figure out what needed to be done to solve this:\nConversation with Harry to check on the feed URLs. Conversation with Harry to check on the feed URLs.\nHarry immediately dove in and started to take a look at the URLs that were given, the Hugo configuration, and what would need to change in order to get the feeds to be working again. Harry provided me with a couple of options.\nHarry presented a couple of recommendations. Harry presented a couple of recommendations.\nI pivoted at this point to suggest that I wanted the pages to where they should be from a Hugo perspective, but also the pages that we had in the previous RSS feeds. Harry was off to the races.\nHarry - The Fix #After I opened up an issue on GitHub with input from Harry. Harry was on it from there. Harry drafted a PR for me to review. Once I approved the PR and merged, I passed that information along and Harry was then onto the verification stages.\nWhy Harry Is Doing This Work I could have done all of this on my own with the assistance, but I am pushing to see what we could enable and become eventual full self servicing.\nThe verification failed however.\nVerification - Failed Verification - Failed\nWith the new information there, we had some conversation that clarified that the blog is now moved away from GitHub Pages and hosted on Cloudflare Pages. With that in mind a new suggestion came in that I was not aware of, but using Cloudflare Redirects. And with that a follow on PR was on the way.\nHarry suggested using Cloudflare Redirects Harry suggested using Cloudflare Redirects\nAfter the follow on PR that put everything into place, we now have all of the feeds being updated. I was able to confirm the following day on Feedly that the previous RSS feeds were now populating articles.\nFeedly now showing articles since the move to Hugo. Feedly now showing articles since the move to Hugo.\nSummary #The RSS feed is fixed, the redirects are in place, and Feedly is pulling articles again. Harry found the Cloudflare Redirects solution - something I hadn\u0026rsquo;t considered. That\u0026rsquo;s the kind of working alongside I\u0026rsquo;m after.\nWhy the name Harry? This was one that I asked my kids to help me out with. At this point it was their choice and it\u0026rsquo;s good to give a name/personality to work alongside.\n-Josh\n","date":"2026-03-14","permalink":"https://josh-v.com/ai-rss-fix/","section":"Posts","summary":"My RSS feeds broke during the Hugo migration. Instead of re-learning the details myself, I handed it to Harry. Here\u0026rsquo;s how it got fixed.","title":"AI: The Quick RSS Feed Fix"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/harry/","section":"Tags","summary":"","title":"Harry"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/life/","section":"Categories","summary":"","title":"Life"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/openclaw/","section":"Categories","summary":"","title":"Openclaw"},{"content":"I have been trying to diagnose a recurring Check Engine light on our family vehicle. It\u0026rsquo;s been a frustrating back and forth with the dealership service center, and what finally got my answer that I needed - it didn\u0026rsquo;t come from the service center, it was my AI assistant.\nThe Dealership Runaround #The first time the light came on, we had the codes scanned and identified. The dealership service center needed to see the code themselves, not just hear what it was. So we continued about our normal days with the vehicle, without harm to driving it and without knowing if it would come back on again or if this would become a major repair.\nA couple of months later the light came on again. At the direction from the service center, we took the vehicle in. This time the dealership was supposed to read the codes and set up a service appointment with the data read, and service it from there. The first thing the tech did once they got the car? Attempted to recreate the error. This takes months to happen at times. The service center rep just read verbatim from the repair notes and couldn\u0026rsquo;t really tell me what the deal was, other than to say we need to capture it when it was happening again. Disappointing.\nSending the Repair Notes to AI #With how much I\u0026rsquo;m in the AI world these days, I said to myself - let\u0026rsquo;s send a note to Harry (my AI assistant built on OpenClaw) and ask him to decipher the repairs. This worked spectacularly, and that\u0026rsquo;s what this post is all about.\nFrom the image of the repair notes, I asked: what do you know about this vehicle, what are the next steps, and should I be concerned?\u0026quot;\nHarry\u0026#39;s response - vehicle details redacted for privacy\nHarry first identified the vehicle, then came the research. He found that this is a common problem, with many owners reporting the same error code. Specifically, it appears to happen after a software update from a recall was applied, and in colder temperatures. Both of which were true for us. And that it does not appear to be a significant issue - but something we\u0026rsquo;ll get taken care of at some point.\nIn a few minutes, I had more useful context than the service rep gave me in person.\nSummary #This is a small example, but it captures something I keep running into: AI is genuinely useful for translating technical information into something actionable. The service center had the data. They just couldn\u0026rsquo;t, or didn\u0026rsquo;t, explain it. AI filled that gap instantly.\nIf you\u0026rsquo;re not using AI for stuff like this yet, just start. Send it a photo, paste in some notes, and ask a question. You might be surprised at what comes back.\n-Josh\n","date":"2026-03-09","permalink":"https://josh-v.com/ai-car-service-translator/","section":"Posts","summary":"When the dealership service rep couldn\u0026rsquo;t explain what was wrong with our vehicle, I sent the repair notes to my AI assistant. What came back was more useful than anything the service center told me.","title":"AI: The Service Department Translator"},{"content":"A year ago I called AI a 50/50 bet. Half the time it worked, half the time I was re-checking and re-prompting. I was paying $20 a month to multiple providers just to keep an eye on things. AI was around, but it wasn\u0026rsquo;t transforming how I worked. Not yet.\nThen a few weeks ago, something clicked. And I burned through my Anthropic and OpenAI credits faster than I thought possible.\nIt\u0026rsquo;s early 2026, and many are pointing to the Opus 4.6 Release and GPT5.3 as a pivotal point in the AI space. I\u0026rsquo;d agree, but I\u0026rsquo;d also say the previous generation of models were already laying the groundwork. What changed was how I started using them.\nOpenClaw Warning If you are looking to go out and run this yourself, please don\u0026rsquo;t jump right into the automated VPS deployment of OpenClaw without thinking and securing it. There are many one-click solutions out there advertising. I do not personally recommend this and in fact would try to persuade you to run on some local hardware that is not from your company.\nThe Ralph Loop #It started with the Ralph Loop. The concept is straightforward: you give the AI a task, it builds, tests, and iterates in a loop until it\u0026rsquo;s done. No hand-holding between steps. I tried it and blew through my credits in no time. Take a look at the spikes in usage: . Those spikes were new features I was building for an app I thought I could ship. Except the paid app I already use came out with all of those features anyway. Which is great! One less thing for me to have to ask those around me to use.\nBut that was just the start.\nDiscovering OpenClaw #For the past couple of weeks I have been using AI to help me out a little more at a time. Using Claude Code both in CLI and in the Desktop App, exploring what its capabilities are. And I have to say that it is pretty awesome.\nThen out of the blue for me (maybe not for others), up pops OpenClaw (Clawdbot -\u0026gt; Moltbot -\u0026gt; OpenClaw). My cousin put out an article on his blog about using Clawdbot at the time to help him in the car buying experience. That\u0026rsquo;s when it started to click about what the capabilities might be. Then I started to see what a few YouTube personalities were doing, and I said yes, I need to be getting in on this. So I spun up a VM on my home compute and I\u0026rsquo;m diving head first. The first week went well, and I\u0026rsquo;m looking forward to doing more.\nI\u0026rsquo;m also looking to dive into local models. I finally have my use case: a 24/7 available AI system that isn\u0026rsquo;t too crazy and isn\u0026rsquo;t too expensive. More on that in a future post.\nThere are multiple components that make up the sauce of OpenClaw and makes it powerful. Some of that is explained more in this video on YouTube.\nMemory #One of the pieces that really sets OpenClaw apart is memory. Using simple Markdown files, the system remembers what you\u0026rsquo;ve talked about from chat to chat: your preferences, your decisions, your context. It\u0026rsquo;s like having an assistant that actually knows you. Many LLMs are just starting to build this in natively, but OpenClaw has it working now.\nSkills #AI Agent Skills are reusable instructions, written in plain English, that teach AI agents how to use tools properly. It\u0026rsquo;s the DRY (Don\u0026rsquo;t Repeat Yourself) principle at its finest. And this isn\u0026rsquo;t unique to OpenClaw. Claude, Gemini, Codex all have them. It is going to take some time to build out the skills needed to get these systems working in tip top shape, but the investment pays off quickly.\nFor example, instead of telling Claude how to format a commit message every single time, you write the skill once and it just knows. Instead of describing your project\u0026rsquo;s testing conventions on every prompt, a skill file handles it. The AI agent systems already have a lot of capability and pattern matching built in. Skills just instruct the systems how to use what they have in their toolbox.\nSummary #So, should I be looking at OpenClaw or Claude Code? I think both. Using AI systems that are directed by a human first and foremost makes sense. Having a person in the loop and making sure that things are being done and meet our standards. That is where we are at in Q1 of 2026 anyway in my mind.\nI went from paying $20 a month to barely touch AI, to burning through credits in days because I couldn\u0026rsquo;t stop building. That shift happened fast, and I don\u0026rsquo;t think I\u0026rsquo;m unique. We are hitting on something here. Matt Shumer captures it well in his post. It\u0026rsquo;s a terrific read, and I agree that we are at a pivotal point.\nThere will be a lot more exciting and challenging things happening. The best advice I can give is be willing to learn and explore. I\u0026rsquo;m looking forward to where the journey is taking me and there are still really good things to come.\nWhat are your thoughts? Comment below.\nAI Editing Disclosure This post was edited with the assistance of Claude Opus 4.6. The ideas and experiences are mine. The AI helped tighten the structure and clarity.\n-Josh\n","date":"2026-02-12","permalink":"https://josh-v.com/welcome-ai-initializing/","section":"Posts","summary":"I went from calling AI a 50/50 bet to burning through API credits in days. Here\u0026rsquo;s what changed, from the Ralph Loop to OpenClaw to AI agent skills, and why I think we\u0026rsquo;re at a pivotal point.","title":"AI: Initializing"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/claude/","section":"Tags","summary":"","title":"Claude"},{"content":"First and foremost is what is my promise to anyone that may read my blog posts, that I will write all of the bulk of the content of the blog posts. I may have some help from AI in some code snippet section type thing. I will definitely use AI to help edit and review the blog post, but the content that is coming to this blog is my own content.\nMy problem here was that I have some compute in my home that I would like to leverage to the best of my ability to do so. But far too often things have remained idle. Couple that thought with the fact that I am diving in on OpenClaw coupled with Claude and I am exploring lots of possible use cases. For this today, I wanted to spin up some VMs that I would then be able to sync into OpenClaw that I have running. This time I decided to take a small bit of time to send the challenge at hand to Claude and see what it would come up with.\nSolution #First, I\u0026rsquo;m following the best practices that are being put out there by the folks at Anthropic of using a Planning session to build out what the plans are and then go build after. I started with the simple prompt that was able to get me a good amount of the way to what we would be looking for.\nFirst Prompt #Just straight forward:\nPrompt I want to use this to build local VMs here. I have some SSH keys that I wish to incorporate and cloud-init through an Ubuntu image.\nFrom this one prompt Claude got to work. It came back and asked me a few clarifying questions, basically asking if it should use Makefile or another system. I said, yeah, let\u0026rsquo;s go forward with Make, which gives me a whole bunch of options on what to build. It went through and was able to get us to about 90% complete. However there was still one thing missing, the documentation.\nDocumentation Prompt #So here comes the follow up prompt:\nPrompt for README Create a README as well. I think it is self explanatory but with it being a git repo, this makes sense\nAt which point the readme was created.\nMissing Items #So now I had the bulk of the idea but there were still just a few outstanding items. Such things over the next few prompts:\nWhere should I import an SSH key for authentication Please update the readme whenever making updates that impact the process At that point I went forward with the process that was created. There were a few quirks that had to be ironed out for my specific host. Such as the bridge interface that we were going to bridge the VMs to. And finally the last materially piece to the process that is now able to manage my entire fleet of VMs on the host:\nConsole Connection Can we build a make console NAME=?? to connect to the console?\nSummary #Previously I had spent hours attempting to get the whole cloud-init capability set up on my virtual environment. Without success. So by leveraging the tools that I have available to me, Claude (and likely other agents) I said, let\u0026rsquo;s tackle this. I went to the Claude Code in a remote VS Code window and tackled the problem. Probably in about 15-30 minutes. Which also includes the downloading of the images. This is quite the world we are living in. This is showing the power and savings to the individuals that are out there creating and looking to get things done.\n","date":"2026-02-07","permalink":"https://josh-v.com/welcome-ai-01/","section":"Posts","summary":"Here we are diving into just one of the time savings samples of what the AI world is able to accomplish these days. This post is going to dig into a simple one that I have been trying to solve for a while and just couldn\u0026rsquo;t get right - Cloud Init virtual machines. I now am spinning up virtual machines in a few seconds, all with the help of Claude.","title":"AI: My Welcome"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/codex/","section":"Tags","summary":"","title":"Codex"},{"content":"I recently was presented with a request to look at where there was a request to validate that a Rack would be unique at a Location in Nautobot. I took a look at the data validation engine and wasn\u0026rsquo;t able to determine a method to make this work there. So here is the solution.\nSolution #The solution to this is to create a Custom Validator. The whole point of the Custom Validators is to allow you to enforce your own business rules while allowing the Open Source project to remain open and general. In the case of keeping Nautobot generic, Racks are allowed to have the same name anywhere. Sometimes within the same location. Which some organizations may have. The project shouldn\u0026rsquo;t enforce something when there is the possibility of the scenario being true.\nImplementation of the Solution #Now let\u0026rsquo;s dive through the steps taken to get the solution built and tested. This is where the inspiration for my previous post on using Python UV for tools and Python version management came to be. Here is the process where I started from scratch:\nUsed the Nautobot App Cookiecutter to create a Sandbox application for me to test the environment Added some test data including a Site and some racks Validated that I could make a rack with the same name as an existing rack Wrote and debug the Custom Validator Tested that the Rack name would now need to be unique Creating an app from the Cookiecutter repo #First step is to get yourself a sandbox Nautobot App for you to be able to work from. To do this\nCookiecutter Bake a Cookiecookiecutter https://github.com/nautobot/cookiecutter-nautobot-app --checkout main --directory=\u0026#34;nautobot-app\u0026#34; The folks (and myself) and NTC have given a bunch of prompts that will fill in various components of the repository as it gets built. This is just the start. Here are what I set for my sandbox repository:\nCookiecutter Input.❯ cookiecutter https://github.com/nautobot/cookiecutter-nautobot-app --checkout main --directory=\u0026#34;nautobot-app\u0026#34; [1/16] codeowner_github_usernames (): jvanderaa [2/16] full_name (Network to Code, LLC): Josh-V [3/16] email (info@networktocode.com): joshv@example.com [4/16] github_org (nautobot): jvanderaa [5/16] app_name (my_app): sandbox_app [6/16] verbose_name (Sandbox App): [7/16] app_slug (sandbox-app): [8/16] project_slug (nautobot-app-sandbox-app): [9/16] repo_url (https://github.com/jvanderaa/nautobot-app-sandbox-app): [10/16] base_url (sandbox-app): [11/16] camel_name (SandboxApp): [12/16] project_short_description (Sandbox App): [13/16] Camel case name of the model class to be created, enter None if no model is needed (SandboxAppExampleModel): [14/16] Select open_source_license 1 - Apache-2.0 2 - Not open source Choose from [1/2] (1): [15/16] docs_base_url (https://docs.nautobot.com): [16/16] docs_app_url (https://docs.nautobot.com/projects/sandbox-app/en/latest): Congratulations! Your cookie has now been baked. It is located at /Users/joshv/projects/nautobot-app-sandbox-app ⚠️⚠️ Before you start using your cookie you must run the following commands inside your cookie: * poetry lock * poetry install * poetry self add poetry-plugin-shell * poetry shell * invoke makemigrations * invoke ruff --fix # this will ensure all python files are formatted correctly, may require `sudo chown -R $USER ./` as migrations may be owned by root Note: The file `development/creds.env` may be automatically created and ignored by git. It can be used to override default environment variables within the docker containers. For each of these steps, I\u0026rsquo;ll make them expandable. Each of the steps after \u0026ldquo;baking the cookie\u0026rdquo; are laid out to include:\nChanging the directory to where the cookie was baked to Running the command poetry lock to make a Poetry lock file Poetry lock output poetry lock command output❯ poetry lock Creating virtualenv sandbox-app in /Users/joshv/projects/nautobot-app-sandbox-app/.venv Updating dependencies Resolving dependencies... (16.2s) Writing lock file Running poetry install to install all of the packages Running poetry self add poetry-plugin-shell to get a Poetry shell, this was a recent change in Poetry 2.0 that separated the creating the shell from other utilities Running poetry shell to activate the virtual environment is recommended. I\u0026rsquo;m going to recommend otherwise to use the command source .venv/bin/activate. I have not had success installing the shell without it being installed in the system Python. source .venv/bin/activate output source .venv/bin/activate command output❯ source .venv/bin/activate source /Users/joshv/projects/nautobot-app-sandbox-app/.venv/bin/activate Running the migrations And then fixing the formatting of the files that the migrations made ","date":"2025-12-20","permalink":"https://josh-v.com/creating-your-own-validation/","section":"Posts","summary":"I recently was presented with a request to look at where there was a request to validate that a Rack would be unique at a Location in Nautobot. I took a look at the data validation engine and wasn\u0026rsquo;t able to determine a method to make this work there. So here is the solution.","title":"Creating Your Own Validation"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/nautobot/","section":"Tags","summary":"","title":"Nautobot"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/python/","section":"Tags","summary":"","title":"Python"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/ssot/","section":"Tags","summary":"","title":"Ssot"},{"content":"The one challenge that I have had for many years is working with Python and the local system. So much so that I have had good discussions with many of the pure Python developers at NTC. It often involved my statements along the lines of putting everything inside of a Docker container or something to that effect. I still distinctly remember my first attempt at installing pyenv completely borked my Ansible environment. A very technical term I know.\nNow today I was suggested to take a look at using Python UV for managing the Python tools and environments. After taking a few steps on the journey I am convinced that this is the method that I am going to be going on.\nMaking the Switch #So the previously used environment consisted of pyenv for managing the versions of Python and Pipx for managing the tools that were installed via Python. Poetry is still the NTC tool of choice for managing Python packages and libraries. So this was a must have to integrate fine with Poetry for the NTC projects. To make the switch happen there is first the uninstall of the current tools and then the instalation of the new system.\nUninstalling Pyenv #The first step is to remove pyenv from being loaded by any shells that may be using, such as by bash (.bashrc) or ZSH (.zshrc). I found this to be a critical step the hard way. So the first thing on my machines since I\u0026rsquo;m using ZSH is to start by commenting out the references to pyenv in that file.\nRemove all pyenv settings from loadingvi ~/.zshrc After commenting out the lines with # I reloaded the shell.\nReload ZSHsource ~/.zshrc In my particular configuration I had two sections to comment out. A PATH statement near the top and near the bottom of the file a pyenv specific section.\nOnce the environment is set to no longer load pyenv, next up is to remove the directory that maintains the pyenv data and removing pyenv that was previously installed via Homebrew.\nRemove pyenv directoryrm -rf ~/.pyenv Uninstall pyenvbrew uninstall pyenv Example Output Uninstalling pyenv ❯ brew uninstall pyenv Uninstalling /opt/homebrew/Cellar/pyenv/2.6.16... (1,407 files, 4.6MB) ==\u0026gt; Autoremoving 3 unneeded formulae: autoconf m4 pkgconf Uninstalling /opt/homebrew/Cellar/pkgconf/2.5.1... (28 files, 532.2KB) Uninstalling /opt/homebrew/Cellar/autoconf/2.72... (72 files, 3.8MB) Uninstalling /opt/homebrew/Cellar/m4/1.4.20... (14 files, 802.1KB) Once the\nRemoving Pipx #Next up is to remove Pipx. This was primarily installed for me to install Python Poetry. This will still be installed, just done via Python UV instead. I had done the installation of pipx via HomeBrew:\nbrew uninstall pipx Brew Cleanup #At the end of removing these applications that were installed for me on Homebrew, I\u0026rsquo;m now running a brew cleanup command to remove the old pieces.\nbrew cleanupbrew cleanup UV For Tools #I\u0026rsquo;m going to skip right past the UV installation steps since the UV documentation handles this nicely. First tool that I\u0026rsquo;m going to need to install is Python Poetry. The docs suggest to use Pipx, but we just removed that in the line before. The installation is actually quite simple. Just a single command of uv tool install poetry.\nInstall Python Poetry with UVuv tool install poetry Example output of installing poetry with UV Installation of Poetry❯ uv tool install poetry Resolved 43 packages in 216ms Prepared 29 packages in 261ms Installed 43 packages in 29ms + anyio==4.12.0 + build==1.3.0 + cachecontrol==0.14.4 + certifi==2025.11.12 + cffi==2.0.0 + charset-normalizer==3.4.4 + cleo==2.1.0 + crashtest==0.4.1 + distlib==0.4.0 + dulwich==0.24.10 + fastjsonschema==2.21.2 + filelock==3.20.1 + findpython==0.7.1 + h11==0.16.0 + httpcore==1.0.9 + httpx==0.28.1 + idna==3.11 + installer==0.7.0 + jaraco-classes==3.4.0 + jaraco-context==6.0.1 + jaraco-functools==4.3.0 + keyring==25.7.0 + more-itertools==10.8.0 + msgpack==1.1.2 + packaging==25.0 + pbs-installer==2025.12.17 + pkginfo==1.12.1.2 + platformdirs==4.5.1 + poetry==2.2.1 + poetry-core==2.2.1 + pycparser==2.23 + pyproject-hooks==1.2.0 + rapidfuzz==3.14.3 + requests==2.32.5 + requests-toolbelt==1.0.0 + shellingham==1.5.4 + tomlkit==0.13.3 + trove-classifiers==2025.12.1.14 + typing-extensions==4.15.0 + urllib3==2.6.2 + virtualenv==20.35.4 + xattr==1.3.0 + zstandard==0.25.0 Installed 1 executable: poetry With that Poetry is now installed.\nCookiecutter Installation #Also as part of the installation in order to get started easily with creating Nautobot Apps is the use of Python CookieCutter. The tool installs in a snap with UV, taking less than half a second to install everything:\nInstall Cookiecutteruv tool install cookiecutter Output during the install of Cookiecutter ❯ uv tool install cookiecutter Resolved 22 packages in 230ms Prepared 12 packages in 116ms Installed 22 packages in 27ms\narrow==1.4.0 binaryornot==0.4.4 certifi==2025.11.12 chardet==5.2.0 charset-normalizer==3.4.4 click==8.3.1 cookiecutter==2.6.0 idna==3.11 jinja2==3.1.6 markdown-it-py==4.0.0 markupsafe==3.0.3 mdurl==0.1.2 pygments==2.19.2 python-dateutil==2.9.0.post0 python-slugify==8.0.4 pyyaml==6.0.3 requests==2.32.5 rich==14.2.0 six==1.17.0 text-unidecode==1.3 tzdata==2025.3 urllib3==2.6.2 Installed 1 executable: cookiecutter Python Versions #The installation of different Python versions is just as easy. It\u0026rsquo;s a uv python install x.y where x, y, and z are the version numbers. Such as 3.13. You can also specify the patch version as well.\nInstalling Python version 3.13uv python install 3.13 This took only a few seconds to download and install!\nInstallation of Python 3.13 with UV Installation example for uv python install 3.13❯ uv python install 3.13 Installed Python 3.13.11 in 578ms + cpython-3.13.11-macos-aarch64-none (python3.13) Summary #At this point I\u0026rsquo;m not going back. I hold myself the right to change or if there are other issues that arise. But the use of Python UV to handle the tools and Python software versions is here to state for a bit for me. What do you think? Let me know in the comments.\n-Josh\n","date":"2025-12-18","permalink":"https://josh-v.com/cleaning-up-python-environment/","section":"Posts","summary":"Recently I updated my Python development environments to use Python UV for managing Python versions and tools on the system. In this post I take you on the journey that I went on to replace pyenv and Pipx.","title":"Cleaning Up Python Environment"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/uv/","section":"Tags","summary":"","title":"Uv"},{"content":"I\u0026rsquo;m back again with the switch back to Hugo Congo themed blog! Why the switch you ask? Well, I just needed a fresh coat of pain on the blog. I am still very happy writing content with mkdocs-material, but I really just wanted to get back to the theme that is built for a blog. It is purely a preference thing, and I think that this will be a nice clean and smooth change. I had to do a little bit of work to make sure that I get RSS squared away, which I think is in a good state. But overall, I\u0026rsquo;m going to plan to stick wit this for a little bit.\nOld Theme #Overall I was pretty happy with the Mkdocs-material theme. I could decide to go back to it again, and because of this I\u0026rsquo;m keeping the set up around for me to make the switch back if I wanted to. But at this point I\u0026rsquo;m likely sticking to what I have with Hugo Congo.\nWhat I Like About Congo #First what I like is that it is built for a blog. The previous world is built for documentation, and I strongly recommend it. I\u0026rsquo;m liking the cleaniness and the good appearance in the mobile site. When I was looking at my previous blog set up with mkdocs, while very functionaly for a mobile site, it was not as clean as I would like.\nWhat I\u0026rsquo;m Going to Miss #What I\u0026rsquo;m going to miss the most is the Admonitions and flexibility from writing updates in Python. I probably could have made a go at it with some more mkdocs updates and making a few tweaks here and there. But that just isn\u0026rsquo;t where I\u0026rsquo;m at with my personal time at this point. I know, there is also the AI side of things that would allow me to get help quicker on what I may want to do. But I\u0026rsquo;m also getting some of these going within the flexiblity of Hugo and Congo theme.\nThe Migration #During the migration I made heavy use of the Google Antigravity IDE. With its help to make a few of the conversions, I was able to get a lot of the work done quickly. There were a few places that I needed to make a few edits. But one of the things that I liked about the migration and using the agent mode was to describe what I was looking for visually and it would take care of all of the CSS for me. Now there may be some minor tweaks that would be a little bit better. But for my personal blog, we are good to go.\nWhat Do I Have Left? #Not a ton. I am going to go back and take a look at the Hugo Admonitions a bit more to see what else I may want there. And I do need to add some images to the page as the cover art. So I have a few minor updates to do there.\nSummary #I\u0026rsquo;m hoping to be more active and having the look of Hugo Congo I hope will keep me more active, and is also a good method of viewing the content for the readers as well.\n-Josh\n","date":"2025-12-16","permalink":"https://josh-v.com/2025-the-reswitch/","section":"Posts","summary":"I\u0026rsquo;m back over to Hugo for my blog content using the Congo theme. Why? Come take a look.","title":"2025 The ReSwitch"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/blog/","section":"Tags","summary":"","title":"Blog"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/api/","section":"Tags","summary":"","title":"Api"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/services/","section":"Tags","summary":"","title":"Services"},{"content":"Recently, a discussion on the internal Slack at Network to Code highlighted the \u0026ldquo;Amazon API Mandate\u0026rdquo; from 2002, a directive widely seen as transformative for Amazon. In the post (which can be found here), the core tenet is clear: All teams will henceforth expose their data and functionality through service interfaces. Upon rereading this mandate, I was immediately reminded of why I am such a strong believer in this strategy. Let\u0026rsquo;s take a deeper dive. First, the mandate included the following requirements:\nAll teams will henceforth expose their data and functionality through service interfaces. Teams must communicate with each other through these interfaces. There will be no other form of interprocess communication allowed: no direct linking, no direct reads of another team’s data store, no shared-memory model, no back-doors whatsoever. The only communication allowed is via service interface calls over the network. It doesn’t matter what technology they use. HTTP, Corba, Pubsub, custom protocols, doesn’t matter. All service interfaces, without exception, must be designed from the ground up to be externalizable. That is to say, the team must plan and design to be able to expose the interface to developers in the outside world. No exceptions. Anyone who doesn’t do this will be fired. Thank you; have a nice day! Introduction #When I first started drafting this post, I centered on the idea of supporting a \u0026ldquo;Services First\u0026rdquo; strategy strictly through API services. While that is the most common starting point, as I reviewed the mandate, I noticed the explicit inclusion of Pub/Sub (Publish-Subscribe) as a valid communication option. This is something I completely agree with; there are many solid Pub/Sub systems available today, such as Kafka, NATS, and MQTT.\nAt Network to Code Professional Services, we are typically centered around automation workflows that enable our customers to get the most out of their networks. The best part about using Nautobot is that the entire system is API-driven. Nautobot Jobs are a great example of how you can take a CLI-based workflow and migrate it into a system that scales and can be consumed by other teams. I wrote about this in a previous post about Nautobot Jobs Execution and in the Nautobot Book.\nWith Nautobot Jobs, it is incredibly easy to start creating your own API service. With minor adaptations to your Python CLI-based workflow, you can create a Nautobot Job that can be launched via API (assuming the requesting system has the proper permissions).\nWhy Nautobot Jobs? #The primary reason aligns with the mandate described above: it is a tried-and-true method for enabling services to be consumed by other teams.\nOn top of that, Nautobot provides all the mechanisms needed to secure and authenticate these API services. Instead of writing an API service from scratch (where you must handle authentication, permissions, logging, and infrastructure), you can get started right away with your business logic.\nPub/Sub and Nautobot #Pub/Sub and Nautobot are a natural combination. By integrating with enterprise-level Pub/Sub systems such as Apache Kafka or NATS, you enable your enterprise teams to react to events regarding Nautobot data in real-time. Out of the box, Nautobot provides the capability to publish events to Redis and Syslog.\nUsing a Pub/Sub system allows downstream teams to react immediately. For example, if someone updates an IP address in Nautobot, you can catch that event and update your systems in near real-time. The scenario we see most often is incorporating devices into monitoring or authentication systems once a device moves from a Planned status to an Active status. Once the device status changes to a production state, an event triggers the addition of that device into the RADIUS system for authentication.\nAnalysis #Network Automation teams should focus heavily on making services available for the network. This might take the form of a self-service portal for a port change (such as a VLAN change form on ServiceNow) or a port update within the data center when new services are ordered.\nInterestingly, I still see direct database access used for interprocess communication far too often. I agree entirely with the mandate: accessing the database directly is rarely a good idea. While read-only access to a database seems harmless, it introduces tight coupling and bypasses the abstraction layer that an API provides. Data should be presented via an API when querying, and workflows should be triggered via API or Pub/Sub.\nSummary #When taking the next step in your automation journey (beyond the initial automation of read-only workflows with network devices), I believe you should look into delivering services via APIs and Pub/Sub systems. This is a natural progression and provides a strong foundation for building a modern network automation strategy.\nI\u0026rsquo;m going to dive into a few specific examples in future posts to show how you can take advantage of these strategies.\n-Josh\n","date":"2025-12-13","permalink":"https://josh-v.com/services-first-a-reminder-of-strong-growth/","section":"Posts","summary":"This post explores why the \u0026ldquo;Amazon API Mandate\u0026rdquo; remains relevant for modern network automation. It highlights how adopting a \u0026ldquo;Services First\u0026rdquo; strategy (using APIs and Pub/Sub systems like Nautobot) can transform CLI-based workflows into scalable, robust services.","title":"Services First - A Reminder of Strong Growth"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/strategy/","section":"Tags","summary":"","title":"Strategy"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/apple/","section":"Categories","summary":"","title":"Apple"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/apple/","section":"Tags","summary":"","title":"Apple"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/automation/","section":"Tags","summary":"","title":"Automation"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/mac/","section":"Tags","summary":"","title":"Mac"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/productivity/","section":"Categories","summary":"","title":"Productivity"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/shortcuts/","section":"Tags","summary":"","title":"Shortcuts"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/siri/","section":"Tags","summary":"","title":"Siri"},{"content":"Managing audio settings on Mac devices can be repetitive, especially when switching between different microphones for calls, recordings, or meetings. Siri Shortcuts provides a powerful way to automate this process.\nThe Problem #I found myself constantly having to go to the audio settings and use the Option click to get to the sub menu that would set the microphone to my external microphone. After doing this manually for several years, I knew there had to be a better way.\nThe Solution #Recently I have been doing a few things with Siri Shortcuts on my Mac, learning a few more capabilities that the tool has. So I took a multiple step approach to getting the microphone to be set, with some help from ChatGPT and Claude.\nFirst I built a Shortcut that would handle just setting the microphone to my Yeti mic. With a little bit of help and the use of the Homebrew SwitchAudioSource command line script I was able to have Siri Shortcuts set the microphone.\nThis is great, but I wanted to automate this further. Next, I verified that I could automate this Shortcut whenever the Bluetooth device connected. I found this capability within the Automation section.\nThe last piece: I didn\u0026rsquo;t want the automation to fail if the device was not connected, whether because I was traveling away from home or using the laptop without the dock attached. Originally I went down the location path, but that didn\u0026rsquo;t help when I was moving around within the house. So I went with a quick script check to determine if the Yeti microphone was attached to the device.\nSetting Up the Shortcut #Let\u0026rsquo;s dive through setting up the automation pieces.\nWith Homebrew you will need to install switchaudio-osx. brew install switchaudio-osx Once installed, validate what devices are available and find your device name. SwitchAudioSource -a Run the command outside of Siri Shortcuts to validate that the script works before integrating it /opt/homebrew/bin/SwitchAudioSource -t input -s \u0026#34;Yeti Stereo Microphone\u0026#34; Once the script works, set up your Shortcuts 1. This will use multiple `Run Shell Script` types, the first of which verifies the microphone is attached to the system. 2. If the first script's output contains the word `Yeti`, the second script executes to change the **input** device to the Yeti microphone. Note that this does not change the audio output; I let the system handle that automatically. 3. In the `Otherwise` section I set some output to help debug if that condition is hit. 4. Name the Shortcut at the top of the interface Set the automation\nTo get the automation to run whenever a particular Bluetooth device connects, use the automation section on the left sidebar to create a new automation. Select \u0026ldquo;Bluetooth\u0026rdquo; as an option. 2. Select your device from the list. Set the option to \u0026ldquo;Is Connected\u0026rdquo; and \u0026ldquo;Run Immediately\u0026rdquo;. If you want to receive confirmation before making the switch, you can use the \u0026ldquo;Run After Confirmation\u0026rdquo; option instead. I am not using this.\nFinally, select the Shortcut that you just created. Using the Shortcut #Now this Shortcut will run immediately when the Bluetooth device connects to your Mac.\nSummary #Siri Shortcuts definitely has some power. There are some interesting quirks to how it works, but it is a tool you can use to help automate your Mac environment. I\u0026rsquo;m likely to dive into a few more areas as well that will help automate my workflows.\n— Josh\n","date":"2025-11-30","permalink":"https://josh-v.com/siri-shortcuts-microphone/","section":"Posts","summary":"\u003cp\u003eManaging audio settings on Mac devices can be repetitive, especially when switching between different microphones for calls, recordings, or meetings. Siri Shortcuts provides a powerful way to automate this process.\u003c/p\u003e","title":"Using Siri Shortcuts to Set Microphone"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/autocon/","section":"Tags","summary":"","title":"Autocon"},{"content":"AutoCon4 and NautoCon@AutoCon are in the books. It was quite the day, and I have to say that it ranks among my best days of the year in Network Automation, right up there with being able to meet up with the Network to Code team earlier in the year. It was great to see many friends from across the community. Hopefully we’ll be able to keep the conversation going in Slack and maintain the momentum.\nPersonal Highlights #I wasn’t able to attend every session due to some unavoidable day-to-day responsibilities, but I want to share a few of the highlights that stood out to me. If I missed anything, it’s not from a lack of effort, just time and priorities.\nNautoCon@AutoCon #This was the first time the Network to Code team has hosted this event, and it delivered a great collection of real-world perspectives from organizations using Nautobot to solve practical problems. The group had a strong discussion. Rather than summarizing it here, I’ll add a comment with a timestamp once the videos are posted.\nThe Hallway Track #I’ll start with my favorite non-Nautobot part of the event: the hallway track. It was great to say hi to familiar faces and meet new ones. I reconnected with several people I rarely see in person, and for that I say THANK YOU, just for showing up and being among others.\nOn the professional side, it was especially rewarding to meet customers of Network to Code who have entrusted us to deliver automation services and real value. It’s important to me that I keep driving the needle forward. If I ever reach a point where I’m not adding value, then it’s time for something different. Thankfully, that’s not the case right now. Things are very much moving forward.\nPresentations Attended #There were multiple things happening for me during the event that kept me from attending every session, but I still caught several. Many were strong, and they sparked ideas I want to keep pursuing.\nOne theme I’ll be writing about more in an upcoming post: there was a fair amount of CLI-driven automation (which is a great starting point), but the variety of automation types was even more encouraging. Across the talks I attended, I saw coverage across much of the device lifecycle. However, the early phases (Design and Pre-Deployment) were largely absent. I’d place the excellent talk by Greg Botts of Intel firmly in the Deployment phase since it focused on configuring hardware.\nGreg Botts — Overhauling Data Center Network Automation: Intel’s Data-Centric Journey #This was a fantastic talk. It covered a lot: a real success story about putting data center automation in place with the right approach. They are well-positioned for the future, with everything needed to scale or handle break/fix replacements with ease. Once the video is published, this is one to prioritize.\nCat Gurinsky — Lifecycle Automation: Troubleshooting, Upgrading \u0026amp; More #Cat walked through her journey in accelerating upgrades of data center gear. My biggest takeaway: iteration matters. This is a staple of successful Network Automation: you start with your experiences and build on them as new ones come.\nChris Grundemann — 2025 State of Network Automation Survey Results #I had to duck out a bit early for this one, but it’s a strong report and helps highlight key trends across the Network Automation community. It was grounded in facts and worth reviewing when the final results become available.\nSummary #Overall, this event remains my favorite non-internal one to attend. From meeting like-minded individuals to exploring the latest trends, it’s an incredible gathering of professionals in the Network Automation space.\nWhat was your favorite part? Any other thoughts?\n— Josh\n","date":"2025-11-21","permalink":"https://josh-v.com/autocon4/","section":"Posts","summary":"\u003cp\u003e\u003ca href=\"https://networkautomation.forum/autocon4\" target=\"_blank\" rel=\"noreferrer\"\u003eAutoCon4\u003c/a\u003e and \u003ca href=\"https://go.networktocode.com/nautocon-autocon\" target=\"_blank\" rel=\"noreferrer\"\u003eNautoCon@AutoCon\u003c/a\u003e are in the books. It was quite the day, and I have to say that it ranks among my best days of the year in Network Automation, right up there with being able to meet up with the Network to Code team earlier in the year. It was great to see many friends from across the community. Hopefully we’ll be able to keep the conversation going in Slack and maintain the momentum.\u003c/p\u003e","title":"Autocon4 Viewpoint"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/automation_events/","section":"Tags","summary":"","title":"Automation_events"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/community/","section":"Tags","summary":"","title":"Community"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/network_automation/","section":"Tags","summary":"","title":"Network_automation"},{"content":"It’s been a while since my last post, life and work have both been full. Today, I’m diving into one of the most quietly critical aspects of network design: time synchronization. Specifically, how to design NTP within an enterprise network and how to think about time zones when correlating logs.\nWe\u0026rsquo;ll look at two main topics in this post: designing NTP (Network Time Protocol) and managing time zone display in logs.\nNetwork Time Protocol (NTP) #Network Time Protocol (NTP) keeps device clocks synchronized, a small detail that can have big operational consequences. Accurate time is essential for correlating logs, troubleshooting issues, and maintaining consistency across systems. In enterprise environments, having a common time source is vital. Whether investigating a cyber incident or reviewing footage from security cameras, synchronized time keeps all data sources in lockstep.\nPrecision Time Protocol For most organizations, second-level precision is sufficient. However, environments requiring microsecond accuracy may use Precision Time Protocol (PTP). I haven’t personally needed that level of precision yet, but it’s worth exploring if your use case demands it.\nTime Sources #The first design decision is whether your organization should maintain its own time source. Common dedicated sources include:\nSatellite clocks: synchronized via GPS signals Radio signals: received from regional broadcast time services Atomic clocks: the reference source behind most public and private time systems Hosted Time Sources If you’re hosting your own time source, monitor it regularly with your observability platform to ensure it’s receiving valid signals rather than relying on its internal oscillator. These devices periodically sync with their upstream references while serving NTP requests to downstream systems.\nWant to learn more about building modern telemetry and observability systems? Check out the book I co-authored with David Flores and Christian Adell: Modern Network Observability. It’s also available on Amazon.\nInternet Clocks #If you don’t want to maintain hardware, you can synchronize with public Internet NTP servers. These servers, often tied directly to atomic clocks, provide highly reliable time.\nA good starting point is the NTP Pool Project, which distributes requests across a global pool of community-run time servers.\nDesign Considerations #Once your primary time source is established, ensure all systems retrieve time from reliable, redundant servers. In most environments, I recommend a primary and secondary NTP source, both enterprise-wide and within each site (data center, campus, or branch). Typically, a Layer 3 device such as a router or core switch provides local NTP services.\nWindows Time Service (W32Time) Microsoft Windows systems don’t use NTP directly. They rely on the Windows Time Service (W32Time), which synchronizes through Active Directory. Ensure your AD servers synchronize from the same authoritative NTP source as the rest of the environment.\nData Center Time #In larger data centers, consider deploying dedicated NTP servers that synchronize with your enterprise’s primary and secondary sources. These servers then provide time to all other equipment, not just network gear, but also hosts, VMs, and monitoring systems.\nVirtual machines should derive time from their hypervisors, which in turn should sync with the NTP servers.\nCampus Designs #In campus networks, edge or core routers can act as NTP sources for local systems. These include Linux-based hosts, networking gear, security cameras, and other business systems. This approach ensures consistency across devices without requiring every host to reach the Internet or the enterprise core.\nBranch Design #At branch sites, especially those with limited infrastructure, a single router can serve as the primary local time source, synchronizing upstream to a central NTP server. Configure a secondary remote source in case the local router or WAN connection fails. This design maintains local accuracy even when external links are unavailable.\nUTC Versus Local Time Zone #When it comes to log timestamps, this decision is often cultural as much as technical. Ideally, all systems should record logs in UTC. However, for organizations operating entirely within one region, using local time can improve usability, as long as everyone understands the offset.\nFor distributed teams across multiple time zones, UTC is almost always the better choice for correlation and analysis.\nEST vs. EDT — They’re the Same, Right? #Not quite. EST (Eastern Standard Time) is UTC−05:00, while EDT (Eastern Daylight Time) is UTC−04:00. The offset changes with daylight saving time. If you want a neutral reference, use Eastern Time (ET) or Eastern Prevailing Time (EPT) to represent whichever offset is currently in effect.\nSummary #Time synchronization may not be glamorous, but it’s foundational. A few seconds of drift can complicate troubleshooting, incident response, or forensic analysis.\nDesign your NTP hierarchy deliberately, monitor it continuously (ideally through your observability platform), and define clearly how your organization handles time zones.\nWhat are your thoughts on these design approaches? Anything you’d add or handle differently?\n— Josh\nAssisted by AI Some editorial support for this article was provided by AI to improve clarity and concision.\n","date":"2025-11-01","permalink":"https://josh-v.com/time_design/","section":"Posts","summary":"\u003cp\u003eIt’s been a while since my last post, life and work have both been full. Today, I’m diving into one of the most quietly critical aspects of network design: \u003cstrong\u003etime synchronization\u003c/strong\u003e. Specifically, how to design NTP within an enterprise network and how to think about time zones when correlating logs.\u003c/p\u003e\n\u003cp\u003eWe\u0026rsquo;ll look at two main topics in this post: designing NTP (Network Time Protocol) and managing time zone display in logs.\u003c/p\u003e","title":"Network Design with NTP"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/network_design/","section":"Categories","summary":"","title":"Network_design"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/network_design/","section":"Tags","summary":"","title":"Network_design"},{"content":"As the AI movement continues to expand its reach into the networking space, the need for an appropriate source of truth for network data becomes more critical than ever. What I have been seeing so far in the industry for networking and AI has been a lot of working on the individual devices one by one. But when looking at leveraging AI for the network, I believe it is best to look at the network as a whole. And that is where the Source of Truth data being stored in Nautobot is going to provide the right information about the network and the relationships between pieces of information - AI thrives on context, and relationships provide that context for more accurate insights and actions.\nWhy a SOT? #The SOT and specifically a Single Source of Truth where Nautobot integrates data from many systems together into one system that is able to build the relationships in the data. As an example, connecting a circuit to a device interface allows AI to understand the circuit\u0026rsquo;s context and relationships.\nUsing a SOT for Populating Other Systems #Using the SoT as the source of information for network systems ensures that the appropriate information is connected. Using the SOT to populate an observability platform would then allow the AI to interface with more than just the SOT in a reliable way. Populating all ancillary tool configurations from the SoT enables deep, reliable connections between systems. Imagine the following workflow:\nReceive a notification from a service provider that circuit A has gone offline. A workflow is able to execute a check to see if there is a redundant circuit at the location, if so, what interface is that connected to. The workflow is able to determine if the secondary circuit is active. Then a secondary workflow can be executed to verify that the site is still available and run secondary checks. Perhaps pausing high bandwidth, low priority data workflows that can sustain some outage time. This workflow can then also grab location information details, such as location of specific onsite equipment such as modems or cabinet information. Without the SOT Data #In the previous workflow scenario, without having the data inside of a SOT like Nautobot, there are several risks in having a successful workflow:\nThe AI would need to analyze configuration data to hypothesize where the secondary circuit, pathway, or device is for the location. This reliance on conjecture rather than structured data significantly increases the risk of AI hallucinations. I\u0026rsquo;ve seen location information housed in SNMP configuration previously, this data is often stale and forgotten about when moving a device between locations or gets blindly copied as a template, indicating that the device is somewhere it is not. When data is not sourced from a single location, data validation and accuracy is easily compromised. Such as location information, if not sourced from somewhere would be typed. It may appear in different formats if not standardized This is where the Data Validation Engine capabilities provide a layer of protection from the risk by enforcing a set of rules Why Nautobot #Nautobot is uniquely positioned to really drive forward with both Network Automation and the AI future. The focus has been not only on getting data into the database and the SOT, but also having the capability to validate that the data is valid.\nThe Challenge of Data Population #One of the biggest challenges that I have seen over the years is worrying about populating \u0026ldquo;bad\u0026rdquo; data into the SoT. This is something that definitely needs to be addressed, but without addressing it at some point in the near future this risk of continuing to have bad data (technical debt) about the network is going to continue to expand. My advice here is to just ^^get started^^, getting the data for the use cases that you are looking for. Don\u0026rsquo;t get into a situation where you are prematurely optimizing the environment.\nPremature Optimization\nAs defined by Gemini (2025-06-03) This is a very common phrase in software development. It refers to optimizing a part of the system before it\u0026rsquo;s clear that it\u0026rsquo;s a bottleneck or even necessary. Spending excessive time fetching data you might need later perfectly fits this description. The full quote often cited is \u0026ldquo;Premature optimization is the root of all evil\u0026rdquo; (or at least most of it) in programming – Donald Knuth.\nNautobot Jobs #Nautobot Jobs are going to be a big part in my opinion of providing guardrails to AI within an organization. With Nautobot Jobs, since they are directly connected with the SOT, you now have a set of predefined jobs that receive input from an AI tool. With the Jobs execution you then get:\nLogging of Job execution, providing the time stamp of who/what system initiated and the job logging of what was executed. RBAC control of what can be executed, by which departments. If done with Git or via your Nautobot App, you can implement an approval workflow involving human oversight for job execution. It is my opinion, at least at the current state in 2025, that there is a need for guardrails in what the AI systems are able to execute on the network. By providing the framework and an API endpoint that can be read by AI, you are able to get the guardrails of what can be done on the network. This principle should also extend to data updates within Nautobot, where Nautobot Jobs are used to update data, thereby preventing direct, uncontrolled modifications to Nautobot data. These guardrails help to provide a Data Governance for AI framework.\nSummary #In the world of advanced networking that is looking to take advantage of network automation and AI systems, Nautobot as an SOT is what makes the most sense to me. Nautobot is able to natively and quickly provide the data relationships that make AI able to reason better, the Jobs framework to establish guardrails, defining what can and cannot be done, and finally, the Jobs framework provides the appropriate logging to understand the what and when. These are all components that, in my view, will enable AI to advance further and faster\nAt ONUG, Network to Code announced NautobotGPT. NautobotGPT is a GPT that offers two key capabilities out of the gate with Nautobot:\nProvide access to proven Nautobot Jobs via Retrieval Augmented Generation (RAG), with content curated by Network to Code. Enable reading data from Nautobot for use within an agent-based framework. AI Editorial Assistance This blog post had editing assistance from Google Gemini. ","date":"2025-06-03","permalink":"https://josh-v.com/critical-role-of-sot/","section":"Posts","summary":"\u003cp\u003eAs the AI movement continues to expand its reach into the networking space, the need for an appropriate source of truth for network data becomes more critical than ever. What I have been seeing so far in the industry for networking and AI has been a lot of working on the individual devices one by one. But when looking at leveraging AI for the network, I believe it is best to look at the network as a whole. And that is where the Source of Truth data being stored in Nautobot is going to provide the right information about the network and the \u003cstrong\u003erelationships\u003c/strong\u003e between pieces of information -  AI thrives on context, and relationships provide that context for more accurate insights and actions.\u003c/p\u003e","title":"Fueling Network AI: The Critical Role of Source of Truth Data"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/containerlab/","section":"Categories","summary":"","title":"Containerlab"},{"content":"Coming out of the 2024 AutoCon2 conference held in Denver the week of November 18th, 2024 - there is one thing that is standing out more so than anything else. Containerlab is a HUGE blowout success. In observing through several of the workshops at the conference on Monday and Tuesday, many were using Containerlab in some fashion. Now, Containerlab has been around for a while, so this isn\u0026rsquo;t a press release of it. But it is re-affirming what many already know, that this is a great tool to be in the network engineer and network automator toolset.\nWhat is Containerlab #Summed up best by the site:\nContainerlab provides a CLI for orchestrating and managing container-based networking labs. It starts the containers, builds a virtual wiring between them to create lab topologies of users choice and manages labs lifecycle.\nSo Containerlab itself does not run containers. It provides a command line method for using Docker native constructs, including containers and networks. This orchestration provides for powerful lab capabilities to make things more easily consumable by engineers. This has really evolved the usage of container images for networking labs.\nBenefits of Network Container Images #There are several benefits to using containers within computing. These include:\nContainer images are typically smaller and more system resource efficient Use fewer resources Which brings capability to run more complex labs on smaller hardware footprints Faster boot times Able to be defined in code/YAML More portable, providing entire OSes that are able to be run on multiple systems and systems types Of all of these capabilities that are being developed, the idea of a digital twin for a network of size starts to become a possibility. The idea of Integration testing your network is the ideal state to have the example of the network built in another environment. What I have found is that this has not historically been something that has been easy to accomplish. From devices on GNS3/EVE-NG requiring different interface names when building out configurations to when doing anything of size, it requires a large lab environment.\nNetwork Device State as a Container #For me the availability of having container immages is still something that is a challenge towards this environment. Containerlab does have the capability to run non-container native if you only have virtual machine images. The VRNetLab Project is used in conjunction with the Containerlab environment. Take a look at the Containerlab documentation on using VRNetlab for more details on the capability to do so.\nSummary #This is real short, if you are just getting started on your lab environment? Use Containerlab as your base. I plan on working to incorporate Containerlab into all of my labs for my ongoing Network Automation development and all of my writing moving forward. Containerlab was heavily used at a majority of the AutoCon2 workshops and also used heavily was GitHub Codespaces to provide the environment to have for the environment. This allows for easier up and running for those that are looking to learn more. There are a lot of great capabilities that are being developed to provide resources. If anything else besides use Containerlab - it would be great ready to learn some new things! That is a very exciting time.\n-Josh\n","date":"2024-12-15","permalink":"https://josh-v.com/containerlab-explodes/","section":"Posts","summary":"\u003cp\u003eComing out of the 2024 AutoCon2 conference held in Denver the week of November 18th, 2024 - there is one thing that is standing out more so than anything else. Containerlab is a \u003cstrong\u003eHUGE\u003c/strong\u003e blowout success. In observing through several of the workshops at the conference on Monday and Tuesday, many were using Containerlab in some fashion. Now, Containerlab has been around for a while, so this isn\u0026rsquo;t a press release of it. But it is re-affirming what many already know, that this is a great tool to be in the network engineer and network automator toolset.\u003c/p\u003e","title":"Containerlab - Popularity Exploding"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/lab/","section":"Categories","summary":"","title":"Lab"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/linux/","section":"Categories","summary":"","title":"Linux"},{"content":"This post is will provide a brief overview of how port binding works in Linux. This topic that will be required for the small series of using Continue.Dev in your local environment, but before addressing the setup of a machine for remote access in a future post, I thought it would be important to quickly create a post regarding the concept of port binding.\nShowing Listening Ports #In Linux you can view all ports that are being listened on by using the netstat or ss commands. This is useful for identifying active ports and their associated services. My preference in 2024 is to use the ss command, which, according to Linux.com, is a more modern version of the netstat.\nss command The ss command-line utility can display stats for the likes of PACKET, TCP, UDP, DCCP, RAW, and Unix domain sockets. The replacement for netstat is easier to use (compare the man pages to get an immediate idea of how much easier ss is). With ss, you get very detailed information about how your Linux machine is communicating with other machines, networks, and services; details about network connections, networking protocol statistics, and Linux socket connections. With this information in hand, you can much more easily troubleshoot various networking issues.\nLet\u0026rsquo;s take a look at an example output of the ss command using the options of -ltn:\n$ ss -ltn State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 128 127.0.0.1:5432 0.0.0.0:* LISTEN 0 128 0.0.0.0:80 0.0.0.0:* LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 128 192.168.100.10:23 0.0.0.0:* LISTEN 0 128 [::1]:5432 [::]:* LISTEN 0 128 [::]:80 [::]:* LISTEN 0 128 [::]:22 [::]:* Generated with AI Instead of leveraging a local system that would show the ports listening on my network, I asked ChatGPT to generate a list of ports that are being listened to on a Linux host showing the output of ss -ltn.\nThe port number, located to the right of the colon, represents the port being listened to. For example, in 0.0.0.0:80, the 0.0.0.0 denotes the listening address, while :80 specifies the port number. In the column Peer Address:Port shows what peer addresses are allowed to connect to the port that is being listened on. The Local Address:Port is the primary area of concern that this post will be diving into.\nIPv4 Port Listening #Lines 4 and 5 feature the address 0.0.0.0, indicating that the service is listening on all IPv4 addresses on the Linux host. On line 4 we have the port number for a local web server and on line 5 we have the port number for SSH. Because of the listening on all IPv4 addresses, you can connect to these ports from any IPv4 address. If there are multiple IP addresses configured in your Linux host, you\u0026rsquo;ll see multiple entries with the same port numbers.\nIPv6 Port Listening #Mirroring the IPv4 behavior, lines 8 and 9 display[::] signifying that the services are listening on all IPv6 addresses. In this example, these services are HTTP (port 80) and SSH (port 22).\nLocalhost Listening #Lines 3 and 7 show two entries for port number 5432, associated with the PostgreSQL database application. TThese entries are configured to listen exclusively on localhost addresses. he listening IPv4 address is 127.0.0.1 (line 3), and the IPv6 address is [::1] (line 7). This configuration prevents external network connections from accessing the port. Applications utilizing localhost listening IP addresses are accessible solely from that local address.\nSpecific IP Listening #While less common, Linux also allows services to listen on specific IP addresses. Line 6 illustrates this with port number 23 and the IPv4 address 192.168.100.10. This configuration restricts access to this port, allowing connections only from interfaces with the IPv4 address 192.168.100.10.\nSummary #In summary I hope you have learned a bit more about how to view and manage ports on Linux hosts. This is just a quick overview of how you should be interpreting the output from ss and other configuration commands that will be upcoming.\nAI Editorial Assistance This blog post had editing assistance from Google Gemini Advanced - 2024-09-21. The structure of the post was not altered and no significant content was added by the editing.\n-Josh\n","date":"2024-09-21","permalink":"https://josh-v.com/linux-port-binding/","section":"Posts","summary":"\u003cp\u003eThis post is will provide a brief overview of how port binding works in Linux. This topic that will be required for the small series of using Continue.Dev in your local environment, but before addressing the setup of a machine for remote access in a future post, I thought it would be important to quickly create a post regarding the concept of port binding.\u003c/p\u003e\n\u003ch2 id=\"showing-listening-ports\" class=\"relative group\"\u003eShowing Listening Ports \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#showing-listening-ports\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eIn Linux you can view all ports that are being listened on by using the \u003ccode\u003enetstat\u003c/code\u003e  or \u003ccode\u003ess\u003c/code\u003e commands. This is useful for identifying active ports and their associated services. My preference in 2024 is to use the \u003ccode\u003ess\u003c/code\u003e command, which, \u003ca href=\"https://www.linux.com/topic/networking/introduction-ss-command/\" target=\"_blank\" rel=\"noreferrer\"\u003eaccording to Linux.com\u003c/a\u003e, is a more modern version of the \u003ccode\u003enetstat\u003c/code\u003e.\u003c/p\u003e","title":"Linux Port Binding"},{"content":"The year 2024 will showcase the remarkable evolution and contributions of AI. One prominent application of AI lies in its ability to streamline the coding process. In this post I demonstrate how to utilize the Continue VS Code plugin as a viable alternative to the GitHub Copilot system. This will allow you to have choice on the AI back end or in this scenario, the capability to self host the AI system using Ollama.\nGetting Code Completion in VSCode #The realm of code completion tools is vast and varied, with standing as a pioneer. This paid subscription service harnesses AI to empower developers with code completion, chat commands, and seamless interaction with their codebase. While a powerful asset, my deep involvement in open-source projects has drawn me to the impressive AI system within the project. My curiosity about its potential led me to the exciting discovery that the plugin can recreate the GitHub Copilot experience within VSCode by leveraging Ollama.\nContinue.dev #The Continue.dev plugin empowers developers in both VS Code and JetBrains environments by seamlessly integrating with a diverse array of AI systems, transforming them into invaluable coding assistants. These AI connections include:\nOllama OpenAI Together Anthropic Mistral LM Studio This flexibility in choosing your AI assistant ensures that developers can tailor their coding experience to their specific needs and preferences. It\u0026rsquo;s a feature that holds tremendous promise, and I\u0026rsquo;m eager to explore its potential.\nOllama: Your Gateway to Open-Source LLM Access #Ollama emerges as a compelling open-source solution, offering the capability to leverage Large Language Models (LLMs) without incurring subscription costs. By harnessing your existing hardware and systems, Ollama provides a cost-effective pathway to integrating powerful AI language capabilities into your projects.\nOllama: Embracing Local Execution #Ollama extends its accessibility across the three major platforms: Linux, MacOS, and Windows. Detailed installation guides tailored to your specific platform are readily available here. If you\u0026rsquo;re equipped with a Mac featuring Apple Silicon, Ollama seamlessly runs directly on your machine. However, for other systems, additional hardware might be necessary to unlock Ollama\u0026rsquo;s full potential.\nIn my case, I repurposed my Dev Workstation, augmenting it with a more powerful graphics card. While my original setup sufficed for non-intensive tasks, the surging prominence of AI prompted this strategic investment. Initiating Ollama via the provided shell script proved effortless. Subsequently, I configured Ollama for network accessibility, enabling its utilization across my local development environment, spanning both my primary development machine and other devices I leverage for coding endeavors.\nExposing Ollama to the Local Network\nI\u0026rsquo;ll be covering this in another blog post soon, the next one up after this!\nWhat is Ollama?\nIf you are looking for more of what Ollama is, perhaps the best route is to review the NetworkChuck YouTube video on it. The short story is that Ollama helps to provide LLM capabilities locally. I combine it with Open WebUI to get the same LLM experience that you get with ChatGPT locally.\nContinue #With Ollama successfully set up, it\u0026rsquo;s time to harness its power within your preferred development environment. Install the Continue plugin for VS Code (while I\u0026rsquo;m demonstrating this in VS Code, it functions seamlessly in JetBrains products as well). Once Continue is installed and configured to connect to your local Ollama instance, you\u0026rsquo;ll unlock real-time code completion suggestions directly within your editor.\nLet\u0026rsquo;s put this into action by creating a new file. I\u0026rsquo;ve named mine try_code_complete.py. I personally use the try_ prefix to denote files that are part of my active experimentation and development process.\nGenerating Code To Start #The first piece of Continue is that you can use the keyboard shortcut Ctrl+I / Cmd+I to bring up a prompt to send to the Ollama model (starcoder2). I sent a request to Generate a hello world python file.\nI then got back the following:\nimport requests import time from colorama import Fore, Style def main(): print(f\u0026#34;{Fore.CYAN}What\u0026#39;s your name? {Style.RESET_ALL}\u0026#34;) name = input() for i in range(5): time.sleep(.1) print( f\u0026#34;{Fore.GREEN}Hello, {name}! {i + 1}/{5} {Style.RESET_ALL}\u0026#34;, end=\u0026#34;\\r\u0026#34; ) if __name__ == \u0026#34;__main__\u0026#34;: main() As with using AI in any system, there are going to be some things that are not quite right at times. In this case, it generated a new to me type of Hello World file that blinked the name entered in via text. However, the blinking happened so fast, that I barely noticed what it was doing initially. The second inacurracy is that the requests library is not used by the script anywhere. So there is no need to import the requests library. Updating the Python code to blink more slowly and removing the requests library I came up with this:\nimport time from colorama import Fore, Style def main(): print(f\u0026#34;{Fore.CYAN}What\u0026#39;s your name? {Style.RESET_ALL}\u0026#34;) name = input() for i in range(5): time.sleep(1) print( f\u0026#34;{Fore.GREEN}Hello, {name}! {i + 1}/{5} {Style.RESET_ALL}\u0026#34;, end=\u0026#34;\\r\u0026#34; ) if __name__ == \u0026#34;__main__\u0026#34;: main() With this updated, I can now see the output a little better, during one of the output prompts I captured:\nWhat\u0026#39;s your name? Josh-V Hello, Josh-V! 4/5 Code Completion #Next up is the code completion. With a new file I started filling in the start of things of having a personal Hello World with providing the comment Add two random numbers together. It then generated the output as one may expect for using random:\nWhat was not completed with the code completion was the import of the library into the file. So just relying on the code completion itself is not a recommended expectation that the code will just work. You need to know where things are going and what can be done.\nWith code completion, I have found that writing the comments of what you intend to do will help out the system significantly. Which when using code completion to assist you in your code writing, will get your code more completely documented.\nCode Tests #As an example, the continue.dev team included a custom command for testing code as an example. The configuration for continue is stored in the ~/.continue/config.json file. This file contains the configurations that are used by the continue.dev tooling. It also includes the custom commands that are available to be run.\nThe JSON snippet for the custom command is:\n\u0026#34;customCommands\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;test\u0026#34;, \u0026#34;prompt\u0026#34;: \u0026#34;{{{ input }}}\\n\\nWrite a comprehensive set of unit tests for the selected code. It should setup, run tests that check for correctness including important edge cases, and teardown. Ensure that the tests are complete and sophisticated. Give the tests just as chat output, don\u0026#39;t edit any file.\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Write unit tests for highlighted code\u0026#34; } ], When using the Continue chat (on the right of the VS Code browser), you can use slash commands to execute the various prompt. For example, to run the test command on the selected code, you would type /test. When this executed the system provided a test that would provide Python Unittest output. While Unittest is great, I prefer to use pytest myself from a readability perspective and output readability. So I created a second chat command, pytest, that would be the same command as seen above in pytest format.\nWhere is the toolbar?\nIn my VS Code, the right hand toolbar was missing. I could not find the Continue plugin anywhere. It was within the Secondary Side Bar. To show the secondary toolbar, use the View menu on VS code. In the Linux version of the app, it is nested in View \u0026gt; Appearance.\n\u0026#34;customCommands\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;pytest\u0026#34;, \u0026#34;prompt\u0026#34;: \u0026#34;{{{ input }}}\\n\\nWrite a comprehensive set of unit tests for the selected code using pytest. It should setup, run tests that check for correctness including important edge cases, and teardown. Ensure that the tests are complete and sophisticated. Give the tests just as chat output, don\u0026#39;t edit any file.\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Write unit tests for highlighted code\u0026#34; } ], Now it gives the results in pytest format. As part of the reading of the code to generate the tests, it suggested that I convert the straight multiplication into a function that calculates the area. So after adding the function, highlighting the function, and using the chat to generate the pytest tests, here is the recommended pytest file:\n# tests/test_file.py import pytest from file import calculate_area @pytest.fixture def rectangle_data(): return [ {\u0026#34;length\u0026#34;: 2, \u0026#34;width\u0026#34;: 3}, {\u0026#34;length\u0026#34;: 4, \u0026#34;width\u0026#34;: 5}, {\u0026#34;length\u0026#34;: 0, \u0026#34;width\u0026#34;: 0}, {\u0026#34;length\u0026#34;: -1, \u0026#34;width\u0026#34;: 2}, ] class TestCalculateArea: @pytest.mark.parametrize(\u0026#34;input_data\u0026#34;, rectangle_data) def test_calculate_area(self, input_data): length = input_data[\u0026#34;length\u0026#34;] width = input_data[\u0026#34;width\u0026#34;] expected_area = length * width assert calculate_area(length, width) == expected_area def test_calculate_area_zero_length(self): length = 0 width = 3 expected_area = 0 assert calculate_area(length, width) == expected_area def test_calculate_area_negative_length(self): length = -1 width = 2 expected_area = 0 # Area of a rectangle with negative length is not defined in the problem, so we set it to 0 for simplicity assert calculate_area(length, width) == expected_area def test_calculate_area_zero_width(self): length = 4 width = 0 expected_area = 0 assert calculate_area(length, width) == expected_area def test_calculate_area_both_zero(self): length = 0 width = 0 expected_area = 0 assert calculate_area(length, width) == expected_area Summary #The AI space is currently undergoing a period of rapid evolution. New ideas and articles are emerging almost daily, highlighting the exciting potential of this technology. Leveraging AI and LLMs for assistance remains an area of active development and innovation. Continue.dev\u0026rsquo;s ability to connect LLMs with IDEs like VS Code and JetBrains products promises further improvements in code quality and development workflows.\nFor me, a prime example of this potential lies in generating unit tests for existing codebases. This offers a powerful method for preventing regression bugs and ensuring code stability.\nWe are only beginning to explore the possibilities of AI-assisted coding. The examples shared here demonstrate the capabilities of Continue.dev and Ollama in streamlining development tasks, from code completion to unit test generation. Crucially, AI should not be viewed as a replacement for writing code but rather as a valuable tool to augment your own skills and expertise. It remains essential to understand the code generated by AI and to ensure adherence to best practices.\nThe combination of Continue.dev, Ollama, and your own coding prowess opens doors to enhanced productivity and code quality. By embracing AI assistance responsibly and strategically, developers can unlock new levels of efficiency and innovation.\nEditorial assistance This blog post had editing assistance from Google Gemini Advanced - 2024-09-07. The structure of the post was not altered and no significant content was added by the editing.\n","date":"2024-09-07","permalink":"https://josh-v.com/continue-dev/","section":"Posts","summary":"\u003cp\u003eThe year 2024 will showcase the remarkable evolution and contributions of AI. One prominent application of AI lies in its ability to streamline the coding process. In this post I demonstrate how to utilize the \u003ca href=\"https://continue.dev\" target=\"_blank\" rel=\"noreferrer\"\u003eContinue\u003c/a\u003e VS Code plugin as a viable alternative to the GitHub Copilot system. This will allow you to have choice on the AI back end or in this scenario, the capability to self host the AI system using \u003ca href=\"https://ollama.com\" target=\"_blank\" rel=\"noreferrer\"\u003eOllama\u003c/a\u003e.\u003c/p\u003e","title":"Code Completion in VS Code with Ollama"},{"content":"Modern Network Observability #I\u0026rsquo;m proud to be one of the three authors to put together the book on Modern Network Observability. It\u0026rsquo;s been a great joy to write and contribute to the Networking community with this book on setting up a modern network observability platform using open source tools. The book aims to help you:\nDefine a composable network observability stack with numerous practical implementations Utilize the network observability stack to address real network operation challenges Learn about obtaining various data types programmatically and how to utilize them to enhance a network automation strategy In the book we dive into an observability pipeline that starts with the metrics collection, how to enhance and transform the data using a source of truth, storing the data for long term retrieval, visualizing the data, and how to leverage the data driving business success.\nTelegraf Prometheus Grafana Who This Book is For #This book is intended for all network engineering roles such as network analysts, administrators, architects, security personnel, support staff, and managers working in both on-premises and cloud environments who are tasked with implementing or using network monitoring solutions. All of these roles will benefit from understanding what can be achieved through the integration of network observability within a network automation strategy. Basic programming knowledge in Python and Go, familiarity with networking concepts, and a basic understanding of Docker containers for the lab scenarios.\nWhat You Will Learn # Learn how network observability improve network operations Understand the main components of a network observability stack Implement a complete solution using Telegraf, Prometheus, and Grafana Identify multiple available solutions and their relevance to the stack Use network observability to empower business decisions How to orchestrate the solutions to simplify managing the stack Get it today! #:simple-amazon: Amazon{ .md-button}\nThank You #I want to express a thank you to David Flores and Christian Adell for including me in the writing of the book. It has been one heck of a journey that we have been on together writing this and an exciting topic to be able to share with the rest of the Network Engineering community.\n","date":"2024-09-07","permalink":"https://josh-v.com/observability/","section":"Josh VanDeraa","summary":"\u003ch1 id=\"modern-network-observability\" class=\"relative group\"\u003eModern Network Observability \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#modern-network-observability\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h1\u003e\u003cp\u003eI\u0026rsquo;m proud to be one of the three authors to put together the book on Modern Network Observability. It\u0026rsquo;s been a great joy to write and contribute to the Networking community with this book on setting up a modern network observability platform using open source tools. The book aims to help you:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eDefine a composable network observability stack with numerous practical implementations\u003c/li\u003e\n\u003cli\u003eUtilize the network observability stack to address real network operation challenges\u003c/li\u003e\n\u003cli\u003eLearn about obtaining various data types programmatically and how to utilize them to enhance a network automation strategy\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eIn the book we dive into an observability pipeline that starts with the metrics collection, how to enhance and transform the data using a source of truth, storing the data for long term retrieval, visualizing the data, and how to leverage the data driving business success.\u003c/p\u003e","title":"Modern Network Observability"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/cisco/","section":"Categories","summary":"","title":"Cisco"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/redux/","section":"Categories","summary":"","title":"Redux"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/redux/","section":"Tags","summary":"","title":"Redux"},{"content":"This may be my favorite post within the realm of what is possible as I write this series. There are many more things that could be done, but we did pretty well considering. Going back over a decade now, I had the fortunate opportunity to work to deploy Guest WiFi 📶 for a large number of retail sites, with the heavy lifting of the work being done within a five month period of time. This post is about the conversion of access points from one management system to another.\nThe Task At Hand: Converting Management System #To better manage the wireless infrastructure, we needed to migrate management systems as a first step before adding any additional infrastructure to meet the expected capacity needs. The deployment aimed to ensure a consistent wireless experience for guests throughout the store. The wireless profile for guest mobile devices differed from everyday store operations.\nThe goal was to get access points to join a new wireless management system, effectively requiring a code upgrade. The process involved converting one store at a time and migrating the wireless system in small batches to ensure seamless transition. We partnered with a team that built a database system to track the migration, verifying all necessary steps were completed. The process was as follows:\nMigration technician logs into the database. Initializes the migration based on the schedule. Starts the process on a few access points, ensuring no area of the retail environment is completely offline. The remote AP joins a staging controller that receives all new access points. Applies the configuration to the wireless AP, including the final controller destination. The AP moves to the new controller, and functionality is verified. This process worked well and paved the way for the next stages of providing Guest WiFi throughout the entire environment.\nHow I Would Accomplish This in 2024 #The overall process was effective, but I would automate it further. Using a database for inventory is foundational for good network automation. The phases I would focus on are now:\nData Gathering Migration Continuing Operations Data Gathering #I would gather data from the original management system and put it into Nautobot as a vendor-agnostic source of truth. This sets up future success for other migrations without relying on a specific system. If these data points were not available from the original management system, I would write some automation to gather the data points. Key data points include:\nData Point Why Needed? Ethernet MAC Address Needed for identification of the device Neighbor Interface Needed for recovery of services if necessary Current Clients Needed for verification of services at the end The MAC address is essential for device identification. For the neighbor interface, I would build out the switches in Nautobot with corresponding interface names and cable connections. I would avoid unnecessary details like cable length or color unless needed for the automation task.\nGathering additional diagnostic data, such as the number of wireless clients and their MAC addresses, would help verify the services.\nWith data available in Nautobot, I can start the migration process.\nMigration #Now the bigger dream that I would have for this migration would to get it fully automated, with logging additions. I would use a long running Job to start the migration. This is something that I would first look towards Nautobot to control the process of initiating the configuration update. I would add a new status into Nautobot of Upgrading. This will allow me to query the Nautobot database to know exactly what access points are in the process of upgrade, and give a quick search while in a loop to determine if the next access point is ready for the upgrade.\nNautobot Task Timeout Of note that the Nautobot Task timeout would need to be increased for this task. Take a look at the Nautobot Docs for further explanation - https://docs.nautobot.com/projects/core/en/stable/user-guide/administration/configuration/optional-settings/#celery_task_soft_time_limit.\nOnce the access point has started the migration, there would need to be a separate task that would be run to complete the controller side of the upgrade. That once an access point was ready for completing the configuration, that the automation to complete the configuration would be completed. Here I would first look to determine if there was a logging mechanism that I could hook into with the wireless LAN controller (WLC). That when the WLC had a new access point join the controller, send a log to a logging destination. Then from the logging destination would fire a webhook to a service that would provision the access point. This I would look to house on a separate Nautobot Job as the easiest getting started perspective. Then this provisioning Nautobot Job would:\nGather the MAC addresses of the access points that are on the staging controller Look up the MAC address inside of Nautobot to determine what the access point configuration should be Provision the access point as prescribed by the source of truth - Nautobot Send the AP to its final controller Log into the final controller, and verify that the AP is providing the services it suggests that it is Change the status of the access point in Nautobot from Upgrading to Active I would then build out a reporting view to show the status of the ongoing sites, the past day of site migrations, and the past week of migrations. I would likely build out a data model to handle the migration data to be able to see the status of the site as it progressed as well.\nContinuing Operations #Now that these processes have been built out, I would look to arm the operations teams with these tools to be able to complete the daily activities, such as break/fix replacement of APs, the re-provisioning of access points, and provide a verification/audit capability to compare the configuration of the APs to the intended design housed with Nautobot (are changes being made locally without updating the source of truth).\nSummary #Overall the process to migrate from one architecture to another architecture over a decade ago went well. There was the start of a source of truth strategy that I look back and that I missed that opportunity on. There may have been a few times that the data gathered during that migration effort was used, but not in what it should look like to make that data available as needed like there is today with something like Nautobot. The human side of the conversion would need to be looked at a little bit further as well. How errors would be handled and escalated as necessary. But by putting the automation in the hands of the system, consistency of the migration would ensure quality and full completion.\nThe Best Part #The Best Part for me? I got to design and implement something very similar. I\u0026rsquo;m very proud of this execution and where that application has gone. Read more about the case study on the Network to Code website -Josh\n","date":"2024-07-17","permalink":"https://josh-v.com/redux-wireless-conversion/","section":"Posts","summary":"\u003cp\u003eThis may be my favorite post within the realm of what is possible as I write this series. There are many more things that could be done, but we did pretty well considering. Going back over a decade now, I had the fortunate opportunity to work to deploy Guest WiFi 📶 for a large number of retail sites, with the heavy lifting of the work being done within a five month period of time. This post is about the conversion of access points from one management system to another.\u003c/p\u003e","title":"Redux: Wireless Conversion"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/wireless/","section":"Categories","summary":"","title":"Wireless"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/nautobot/","section":"Categories","summary":"","title":"Nautobot"},{"content":"In the first release of Nautobot all of the Jobs were Atomic by default. This was from the previous focus of the legacy source application that assumed that scripts/reports would only be run on the data locally, so by that nature, the jobs should be atomic. As more and more Jobs started to interact with other systems, it became apparent that there needed to be a control mechanism provided (as I understand). So the introduction of a context manager and decorator was brought to the table to provide the same previous functionality while changing of the default behavior in Nautobot 2.x+.\nWriting that Atomic Job #Let\u0026rsquo;s take a look at using an example Job that will create 3 location types and 3 locations, 1 of each of those types. I will purposely put an error in the data to demonstrate the atomic nature. The file structure looks like the following for my sandbox environment, which was generated from the Nautobot App Cookiecutter:\nTree output of file structure 1 2 3 4 5 6 7 8 9 10 sandbox/ ├── app-config-schema.json ├── __init__.py ├── jobs │ ├── demo_jobs.py │ ├── __init__.py └── tests ├── __init__.py ├── test_api.py └── test_basic.py Init File #First the __init__.py file contains the registration information for the Jobs. The register_jobs() function on line 6 is what will register the jobs. It takes in the list of Jobs that are exploded out with the *jobs call.\njobs/init.py 1 2 3 4 5 6 \u0026#34;\u0026#34;\u0026#34;Jobs definition.\u0026#34;\u0026#34;\u0026#34; from nautobot.apps.jobs import register_jobs from .demo_jobs import CreateSites jobs = [CreateSites] register_jobs(*jobs) Job File #The job itself as you could make out will be in the demo_jobs.py file. For demonstration purposes, I will be putting the data right into the file. For a more production like environment, you would likely have a file that gets fed to the job. For this you will see that I have added slug to two of the locations defined on lines 44-49. These lines are highlighted.\njobs/demo_jobs.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 \u0026#34;\u0026#34;\u0026#34;Demonstration of Jobs.\u0026#34;\u0026#34;\u0026#34; from nautobot.apps.jobs import Job from nautobot.dcim.models import LocationType, Location from nautobot.extras.models import Status name = \u0026#34;Sandbox Jobs\u0026#34; status_active = Status.objects.get(name=\u0026#34;Active\u0026#34;) class CreateSites(Job): \u0026#34;\u0026#34;\u0026#34;Create Sites Job.\u0026#34;\u0026#34;\u0026#34; class Meta: \u0026#34;\u0026#34;\u0026#34;Meta Class.\u0026#34;\u0026#34;\u0026#34; name = \u0026#34;Create Sites\u0026#34; description = \u0026#34;Create Defined Sites\u0026#34; commit_default = False def run(self): \u0026#34;\u0026#34;\u0026#34;Run Method.\u0026#34;\u0026#34;\u0026#34; # Perform the work of the job # Create location types location_types = [ {\u0026#34;name\u0026#34;: \u0026#34;Datacenter\u0026#34;}, {\u0026#34;name\u0026#34;: \u0026#34;Office\u0026#34;}, {\u0026#34;name\u0026#34;: \u0026#34;Remote\u0026#34;}, ] location_type_objects = {} for location_type in location_types: self.logger.info(f\u0026#34;Creating location type {location_type[\u0026#39;name\u0026#39;]}\u0026#34;) location_type_obj, _created = LocationType.objects.get_or_create(**location_type) if _created: self.logger.info(\u0026#34;Created location type %s\u0026#34;, location_type_obj) else: self.logger.info(\u0026#34;Location type %s already exists\u0026#34;, location_type_obj) location_type_obj.validated_save() # Add to the location_type_objects dictionary with the name as the key location_type_objects[location_type[\u0026#34;name\u0026#34;]] = location_type_obj # Create sites sites = [ {\u0026#34;name\u0026#34;: \u0026#34;Datacenter 1\u0026#34;, \u0026#34;location_type\u0026#34;: \u0026#34;Datacenter\u0026#34;}, # This has some bad data, slug is no longer part of the system {\u0026#34;name\u0026#34;: \u0026#34;Office 1\u0026#34;, \u0026#34;slug\u0026#34;: \u0026#34;office_1\u0026#34;, \u0026#34;location_type\u0026#34;: \u0026#34;Office\u0026#34;}, {\u0026#34;name\u0026#34;: \u0026#34;Remote 1\u0026#34;, \u0026#34;slug\u0026#34;: \u0026#34;remote_1\u0026#34;, \u0026#34;location_type\u0026#34;: \u0026#34;Remote\u0026#34;}, ] for site in sites: self.logger.info(f\u0026#34;Creating site {site[\u0026#39;name\u0026#39;]}\u0026#34;) site_dictionary = { \u0026#34;name\u0026#34;: site[\u0026#34;name\u0026#34;], \u0026#34;location_type\u0026#34;: location_type_objects[site[\u0026#34;location_type\u0026#34;]], \u0026#34;status\u0026#34;: status_active, } if site.get(\u0026#34;slug\u0026#34;): site_dictionary[\u0026#34;slug\u0026#34;] = site[\u0026#34;slug\u0026#34;] site_obj, _created = Location.objects.get_or_create(**site_dictionary) if _created: self.logger.info(\u0026#34;Created site %s\u0026#34;, site_obj) else: self.logger.info(\u0026#34;Site %s already exists\u0026#34;, site_obj) site_obj.validated_save() On the first execution of this Job where there is not an Atomic transaction set, you will expect to have all of the Location Types created, the first location. The job will fail. You can see where that job failed in that the logging indicated a site was being created, but there was not a creation.\nBut the objects will stay created as shown below.\nAdd In Error Handling #Let\u0026rsquo;s add some better logging on the job here to report an error.\nUpdated job code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 \u0026#34;\u0026#34;\u0026#34;Demonstration of Jobs.\u0026#34;\u0026#34;\u0026#34; from django.core.exceptions import FieldError from nautobot.apps.jobs import Job from nautobot.dcim.models import LocationType, Location from nautobot.extras.models import Status name = \u0026#34;Sandbox Jobs\u0026#34; status_active = Status.objects.get(name=\u0026#34;Active\u0026#34;) class CreateSites(Job): \u0026#34;\u0026#34;\u0026#34;Create Sites Job.\u0026#34;\u0026#34;\u0026#34; class Meta: \u0026#34;\u0026#34;\u0026#34;Meta Class.\u0026#34;\u0026#34;\u0026#34; name = \u0026#34;Create Sites\u0026#34; description = \u0026#34;Create Defined Sites\u0026#34; commit_default = False def run(self): \u0026#34;\u0026#34;\u0026#34;Run Method.\u0026#34;\u0026#34;\u0026#34; # Perform the work of the job # Create location types location_types = [ {\u0026#34;name\u0026#34;: \u0026#34;Datacenter\u0026#34;}, {\u0026#34;name\u0026#34;: \u0026#34;Office\u0026#34;}, {\u0026#34;name\u0026#34;: \u0026#34;Remote\u0026#34;}, ] location_type_objects = {} for location_type in location_types: self.logger.info(f\u0026#34;Creating location type {location_type[\u0026#39;name\u0026#39;]}\u0026#34;) location_type_obj, _created = LocationType.objects.get_or_create(**location_type) if _created: self.logger.info(\u0026#34;Created location type %s\u0026#34;, location_type_obj) else: self.logger.info(\u0026#34;Location type %s already exists\u0026#34;, location_type_obj) location_type_obj.validated_save() # Add to the location_type_objects dictionary with the name as the key location_type_objects[location_type[\u0026#34;name\u0026#34;]] = location_type_obj # Create sites sites = [ {\u0026#34;name\u0026#34;: \u0026#34;Datacenter 1\u0026#34;, \u0026#34;location_type\u0026#34;: \u0026#34;Datacenter\u0026#34;}, # This has some bad data, slug is no longer part of the system {\u0026#34;name\u0026#34;: \u0026#34;Office 1\u0026#34;, \u0026#34;slug\u0026#34;: \u0026#34;office_1\u0026#34;, \u0026#34;location_type\u0026#34;: \u0026#34;Office\u0026#34;}, {\u0026#34;name\u0026#34;: \u0026#34;Remote 1\u0026#34;, \u0026#34;slug\u0026#34;: \u0026#34;remote_1\u0026#34;, \u0026#34;location_type\u0026#34;: \u0026#34;Remote\u0026#34;}, ] for site in sites: self.logger.info(f\u0026#34;Creating site {site[\u0026#39;name\u0026#39;]}\u0026#34;) site_dictionary = { \u0026#34;name\u0026#34;: site[\u0026#34;name\u0026#34;], \u0026#34;location_type\u0026#34;: location_type_objects[site[\u0026#34;location_type\u0026#34;]], \u0026#34;status\u0026#34;: status_active, } if site.get(\u0026#34;slug\u0026#34;): site_dictionary[\u0026#34;slug\u0026#34;] = site[\u0026#34;slug\u0026#34;] try: site_obj, _created = Location.objects.get_or_create(**site_dictionary) if _created: self.logger.info(\u0026#34;Created site %s\u0026#34;, site_obj) else: self.logger.info(\u0026#34;Site %s already exists\u0026#34;, site_obj) site_obj.validated_save() except FieldError: self.logger.error(f\u0026#34;Failed to create site {site[\u0026#39;name\u0026#39;]}\u0026#34;) raise FieldError(f\u0026#34;Failed to create site {site[\u0026#39;name\u0026#39;]}\u0026#34;) Now with the error handling in place to catch the field error, you get the output of what failed as well as a log message indicating that there was a failure.\nMaking a Job Atomic #Let\u0026rsquo;s look at making this atomic in nature so that all of the objects are created only when the entire job succeeds. Before making any update to the code, let\u0026rsquo;s check out the Locations interface.\nThe code below fixes just one of the lines to remove slug from the data that is being fed in. This way we can demonstrate that we will only still see the single site and get the Atomic nature. That line change is on line 50. The highlighted lines show the two changes necessary to make the Nautobot Job an atomic job.\nFinal Job with Atomic Transaction 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 \u0026#34;\u0026#34;\u0026#34;Demonstration of Jobs.\u0026#34;\u0026#34;\u0026#34; from django.core.exceptions import FieldError from django.db import transaction from nautobot.apps.jobs import Job from nautobot.dcim.models import LocationType, Location from nautobot.extras.models import Status name = \u0026#34;Sandbox Jobs\u0026#34; status_active = Status.objects.get(name=\u0026#34;Active\u0026#34;) class CreateSites(Job): \u0026#34;\u0026#34;\u0026#34;Create Sites Job.\u0026#34;\u0026#34;\u0026#34; class Meta: \u0026#34;\u0026#34;\u0026#34;Meta Class.\u0026#34;\u0026#34;\u0026#34; name = \u0026#34;Create Sites\u0026#34; description = \u0026#34;Create Defined Sites\u0026#34; commit_default = False @transaction.atomic def run(self): \u0026#34;\u0026#34;\u0026#34;Run Method.\u0026#34;\u0026#34;\u0026#34; # Perform the work of the job # Create location types location_types = [ {\u0026#34;name\u0026#34;: \u0026#34;Datacenter\u0026#34;}, {\u0026#34;name\u0026#34;: \u0026#34;Office\u0026#34;}, {\u0026#34;name\u0026#34;: \u0026#34;Remote\u0026#34;}, ] location_type_objects = {} for location_type in location_types: self.logger.info(f\u0026#34;Creating location type {location_type[\u0026#39;name\u0026#39;]}\u0026#34;) location_type_obj, _created = LocationType.objects.get_or_create(**location_type) if _created: self.logger.info(\u0026#34;Created location type %s\u0026#34;, location_type_obj) else: self.logger.info(\u0026#34;Location type %s already exists\u0026#34;, location_type_obj) location_type_obj.validated_save() # Add to the location_type_objects dictionary with the name as the key location_type_objects[location_type[\u0026#34;name\u0026#34;]] = location_type_obj # Create sites sites = [ {\u0026#34;name\u0026#34;: \u0026#34;Datacenter 1\u0026#34;, \u0026#34;location_type\u0026#34;: \u0026#34;Datacenter\u0026#34;}, {\u0026#34;name\u0026#34;: \u0026#34;Office 1\u0026#34;, \u0026#34;location_type\u0026#34;: \u0026#34;Office\u0026#34;}, # This has some bad data, slug is no longer part of the system {\u0026#34;name\u0026#34;: \u0026#34;Remote 1\u0026#34;, \u0026#34;slug\u0026#34;: \u0026#34;remote_1\u0026#34;, \u0026#34;location_type\u0026#34;: \u0026#34;Remote\u0026#34;}, ] for site in sites: self.logger.info(f\u0026#34;Creating site {site[\u0026#39;name\u0026#39;]}\u0026#34;) site_dictionary = { \u0026#34;name\u0026#34;: site[\u0026#34;name\u0026#34;], \u0026#34;location_type\u0026#34;: location_type_objects[site[\u0026#34;location_type\u0026#34;]], \u0026#34;status\u0026#34;: status_active, } if site.get(\u0026#34;slug\u0026#34;): site_dictionary[\u0026#34;slug\u0026#34;] = site[\u0026#34;slug\u0026#34;] try: site_obj, _created = Location.objects.get_or_create(**site_dictionary) if _created: self.logger.info(\u0026#34;Created site %s\u0026#34;, site_obj) else: self.logger.info(\u0026#34;Site %s already exists\u0026#34;, site_obj) site_obj.validated_save() except FieldError: self.logger.error(f\u0026#34;Failed to create site {site[\u0026#39;name\u0026#39;]}\u0026#34;) raise FieldError(f\u0026#34;Failed to create site {site[\u0026#39;name\u0026#39;]}\u0026#34;) When this code runs, even though there was now another site that would be created - Office 1, there is an error that occurs with trying to create Remote 1. That is that the slug is not actually a valid key word argument. Thus the failure:\nAnd when you look at the locations, the site Office 1 is not there, because the database changes were rolled back as part of the atomic transaction.\nAtomic Job Design Considerations #When working with atomic based transactions, it is recommended (at least from me) to handle as much of the database based work early in the job. Any database transaction will be rolled back automatically as part of the atomic nature of the job. However, any work with third party APIs or systems (such as network devices) will be automatic as part of this. You will need to design the same atomic nature in the third party systems.\nSummary #With the adding of an atomic transaction type from the Django project we are able to make sure that the Job completes successfully before saving the data to the database. If any part of the job fails you are able to have the data get rolled back. Design considerations should be taken into account with atomic jobs that interact with third party systems. This is a powerful component that comes part of the general nature of working with databases. But proper care needs to be taken into account.\nLet me know your thoughts! Comment below or on social media. Give it a share.\n-Josh\n","date":"2024-07-04","permalink":"https://josh-v.com/nautobot-atomic-jobs/","section":"Posts","summary":"\u003cp\u003eIn the first release of Nautobot all of the Jobs were Atomic by default. This was from the previous focus of the legacy source application that assumed that scripts/reports would only be run on the data locally, so by that nature, the jobs should be atomic. As more and more Jobs started to interact with other systems, it became apparent that there needed to be a control mechanism provided (as I understand). So the introduction of a context manager and decorator was brought to the table to provide the same previous functionality while changing of the default behavior in Nautobot 2.x+.\u003c/p\u003e","title":"Nautobot: Atomic Jobs"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/network_automation/","section":"Categories","summary":"","title":"Network_automation"},{"content":"Nautobot Book # { align=right width=\u0026ldquo;400\u0026rdquo; }\nI\u0026rsquo;m proud to have been an author in the Network Automation with Nautobot found on Amazon book that has recently been released. The book covers:\nLearn how documenting your network in Nautobot can accelerate your network automation journey Apply NetDevOps to your network by leveraging Nautobot as a network source of truth Minimize tool sprawl by extending, using, or building Nautobot Apps Nautobot enables network teams to build a scalable and extensible network source of truth that provides a foundation to power any network automation stack.\nWith the help of this guide, you\u0026rsquo;ll learn how to deploy, manage, and integrate Nautobot as a source of truth and network automation platform. As you progress, you\u0026rsquo;ll learn what a network source of truth is, the relationship between data and network automation, and network data models. You\u0026rsquo;ll also gain a broad understanding of Nautobot and its robust features that allow maximum flexibility. A dedicated section will show you how to construct a single source of truth with Nautobot and help you explore its programmatic APIs, including REST APIs, GraphQL, webhooks, and Nautobot Job Hooks. Later, you\u0026rsquo;ll develop custom data models and custom apps for Nautobot and experience its extensibility and powerful developer API. In the last part of this book, you\u0026rsquo;ll discover how to deploy configuration compliance and automated remediation once Nautobot is deployed as a network source of truth.\nBy the end of this book, you\u0026rsquo;ll be able to design and manage Nautobot as a network source of truth, understand its key features, and extend Nautobot by creating custom data models and apps that suit your network and your team.\nWhat you will learn # Understand network sources of truth and the role they play in network automation architecture Gain an understanding of Nautobot as a network source and a network automation platform Convert Python scripts to enable self-service Nautobot Jobs Understand how YAML files in Git can be easily integrated into Nautobot Get to grips with the NetDevOps ecosystem around Nautobot and its app ecosystem Delve into popular Nautobot Apps including Single Source of Truth and Golden Config Who this book is for #This book is for network engineers, network automation engineers, and software engineers looking to support their network teams by building custom Nautobot Apps. A basic understanding of networking (e.g. CCNA) and knowledge of the fundamentals of Linux, Python programming, Jinja2, YAML, and JSON are needed to get the most out of this book.\n","date":"2024-06-24","permalink":"https://josh-v.com/nautobot_book/","section":"Josh VanDeraa","summary":"\u003ch1 id=\"nautobot-book\" class=\"relative group\"\u003eNautobot Book \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#nautobot-book\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h1\u003e\u003cp\u003e\u003cfigure\u003e\n  \u003cimg src=\"https://m.media-amazon.com/images/I/81tjWDoYk3L._SL1500_.jpg\" alt=\"Nautobot Book Cover\" class=\"mx-auto my-0 rounded-md\"\u003e\u003c/figure\u003e{ align=right width=\u0026ldquo;400\u0026rdquo; }\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m proud to have been an author in the \u003ca href=\"https://a.co/d/7JPvYpC\" target=\"_blank\" rel=\"noreferrer\"\u003eNetwork Automation with Nautobot found on Amazon\u003c/a\u003e book that has recently been released. The book covers:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eLearn how documenting your network in Nautobot can accelerate your network automation journey\u003c/li\u003e\n\u003cli\u003eApply NetDevOps to your network by leveraging Nautobot as a network source of truth\u003c/li\u003e\n\u003cli\u003eMinimize tool sprawl by extending, using, or building Nautobot Apps\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003eNautobot enables network teams to build a scalable and extensible network source of truth that provides a foundation to power any network automation stack.\u003c/p\u003e","title":"Nautobot Book"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/netdevops/","section":"Tags","summary":"","title":"Netdevops"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/network/","section":"Categories","summary":"","title":"Network"},{"content":"At a previous position to joining Network to Code I was asked to help to build automation to help with the configuration of switches going into a MLS stadium. The stadium was under construction and the network build out would take place at the same time as the stadium was being built out. It was definitely a first and maybe only opportunity that I would have to build out a new stadium.\nScenario #The task at hand is that each of the ports would need to be configured leveraging a good L2/L3 separation with each of the service providers that provide a service to the stadium their own network segment to work through. A large number of ports were going to need to be configured.\nWhat was Automated #There were two primary components that were automated at the onset of the project. The first part was building automation to handle the provisioning of several hundred (400+) access points. Since this was in the late 2010s, WiFi 📶 would be a key part of the system.\nWireless Access Point Provisioning #I developed a provisioning script for the wireless LAN controller to handle over 400 access points. The script read data from a spreadsheet designed by the wireless architect and provisioned the access points into the correct WLAN groups, setting their radios according to the plan. The automation impressed the wireless architect with its flawless execution.\nSwitchport Configuration #Diving into the bigger day to day component of the stadium build would be the configuration of the access ports for all of the vendors. At this point in time, there was not a good plan of exactly which vendor would be plugging into which port, which would feed back to each of the IDFs for the stadium. This is something that was planned for to be dynamic in nature and would need to be tweaked as each vendor provisioned their environment.\nEach of the switch interfaces would get a configuration template for the particular VLAN, and the variables were controlled in an Infrastructure as Code (IaC) methodology. Ansible Ansible would be the automation engine and orchestrator in the environment, using Ansible AWX to handle the Ansible items. Every morning at 9am (or somewhere around there), Ansible AWX would kick off the automation and re-provision every access port to match that of the configuration in the IaC definition.\nThis was great for having consistency and having the definition of the interfaces.\nWhat I Should Have Done Differently #Reflecting on the project, there are two areas I would approach differently:\nCI/CD Deployment Source of Truth Automated Testing CI/CD Deployment #The first thing that I should have built out better at the time, if I were given the right time to do so, a proper CI/CD pipeline like Jenkins Jenkins. The only complaint about adopting the Infrastructure as Code deployment from the team of network engineers was that they couldn\u0026rsquo;t get it done when they needed it to get done. So there would be ports that would get provisioned on say a Tuesday afternoon while working with the vendor. The engineer would miss getting the update into the system and the next morning their configuration would be wiped out. If I had built out a proper CI/CD system, since the deployment part of the CI/CD was already happening, then I believe that the adoption of the port update via IaC would have been significantly improved.\nSource of Truth #The second is that I would look to bring in Source of Truth as that inventory source for the interfaces. There are much more controls that can be brought forward to define the intended state of the interfaces. This would allow for the visualization of the relationships between the interfaces, the VLANs, and the L3 information. I would then incorporate webhooks on changes to the interfaces to automatically deploy the updated configuration as needed.\nAutomated Testing of the Environment #I would have loved for the opportunity to have driven the technology assurance of the environment a lot further back then. It was an idea that I had back in 2018/2019 when it was being built. I had started to tinker with the idea of position a few Raspberry Pi devices around the stadium and writing a script that would join the various WiFi networks, including the fan WiFi network. Then report back on the ability to join or not. There ended up being a few other things that occupied the time of troubleshooting that in the end really should have been spent on the automated testing. But that was not a priority at the time to get completed.\nSummary #To wrap this up, I think that I had an overall positive experience automating the MLS stadium that I was involved in. There are definitely some things that I would have pushed for sooner. The experience of building the stadium network is not something that I will forget any time soon. But at the same time, it isn\u0026rsquo;t something that I would say I must do again. It turns out that the priority isn\u0026rsquo;t the build of the network with a stadium being built. It is the construction of the stadium itself. Rightfully so. Building a stadium network brings some unique challenges and some unique opportunities as well. Thankfully the Network Automation community has some great tools to work with that are vastly different from the past several decades, and exploration of the use of those tools together are what bring together a great story.\n","date":"2024-06-19","permalink":"https://josh-v.com/stadium-automation/","section":"Posts","summary":"\u003cp\u003eAt a previous position to joining Network to Code I was asked to help to build automation to help with the configuration of switches going into a MLS stadium. The stadium was under construction and the network build out would take place at the same time as the stadium was being built out. It was definitely a first and maybe only opportunity that I would have to build out a new stadium.\u003c/p\u003e\n\u003ch2 id=\"scenario\" class=\"relative group\"\u003eScenario \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#scenario\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eThe task at hand is that each of the ports would need to be configured leveraging a good L2/L3 separation with each of the service providers that provide a service to the stadium their own network segment to work through. A large number of ports were going to need to be configured.\u003c/p\u003e","title":"Redux: Stadium Automation"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/nornir/","section":"Categories","summary":"","title":"Nornir"},{"content":"Nornir includes a function that allows for the transformation of inventory data, a feature integrated within the Nornir platform itself. The documentation for Nornir 3.0 is somewhat sparse regarding the usage of Transform functions, so I often refer to the more comprehensive 2.5 documentation. According to the Nornir documentation:\nA transform function is a plugin that manipulates the inventory independently from the inventory plugin used. Useful to extend data using the environment, a secret store or similar.\nI\u0026rsquo;m going to highlight the aspect of extending data, particularly through a secret store but using Environment Variables in the example. This approach is essential for using Nornir applications effectively, as it leverages a secret store to gather sensitive information. While applications like Nautobot provide data about the devices themselves, they are not designed to handle cryptographic secrets.\nGetting Started - Environment Variables #One of the first method we will explore is adding data such as a network device username from the environment. This way you do not need anything other than a command line (Linux/Mac is covered here) to the shell, which is exactly where many run their Nornir applications from. If you would like more on setting environment variables, check out the Twilio blog post that dives in more.\nUse Existing In Nornir Utils If Looking For Environment Variables\nAdded 2024-06-06:\nThe use of environment variables in this case is meant to show as an example. The nornir-utils project has a more production ready version of this same thing. Look to use that if using Environment variables - https://github.com/nornir-automation/nornir_utils/blob/master/nornir_utils/plugins/inventory/transform_functions.py. You should look to use a secrets management system if you have one available.\nPython packaging environment\nFor this post I will be using Python Poetry to handle the packaging within the demo. Python Poetry is the environment that I\u0026rsquo;m most familiar with on using for package management at this time.\nWe will first set the environment variables that will be needed for the Nornir script.\nSet Nautobot environment variablesexport NAUTOBOT_URL=\u0026#34;https://demo.nautobot.com\u0026#34; export NAUTOBOT_TOKEN=\u0026#34;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\u0026#34; Follow Along or Set Your Own With the envrionment variables set above, you will be able to follow along at home as long as the devices haven\u0026rsquo;t been modified on the demo instance. It is a demo instance after all. If you wish to use your own Nautobot instance, just set the NAUTOBOT_URL and NAUTOBOT_TOKEN above appropriately.\nNow that all of the environment variables are set for the Nautobot environment, we can look at the slightly modified Nornir Inventory example with the Demo Nautobot instance.\nPython Dependencies The following Python dependencies will be required to run these examples:\n[tool.poetry.dependencies] python = \u0026#34;^3.9\u0026#34; nornir = \u0026#34;^3.4.1\u0026#34; nornir-nautobot = \u0026#34;^3.2.0\u0026#34; nornir-nautobot==3.2.0 ; python_version \u0026gt;= \u0026#34;3.9\u0026#34; and python_version \u0026lt; \u0026#34;4.0\u0026#34; nornir==3.4.1 ; python_version \u0026gt;= \u0026#34;3.9\u0026#34; and python_version \u0026lt; \u0026#34;4.0\u0026#34; explore-nornir-transform.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 \u0026#34;\u0026#34;\u0026#34;Testing file.\u0026#34;\u0026#34;\u0026#34; import os import urllib3 from nornir import InitNornir # Disable InsecureRequestWarning urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def main(): \u0026#34;\u0026#34;\u0026#34;Nornir testing.\u0026#34;\u0026#34;\u0026#34; location = [\u0026#34;ORD01\u0026#34;] my_nornir = InitNornir( inventory={ \u0026#34;plugin\u0026#34;: \u0026#34;NautobotInventory\u0026#34;, \u0026#34;options\u0026#34;: { \u0026#34;nautobot_url\u0026#34;: os.getenv(\u0026#34;NAUTOBOT_URL\u0026#34;), \u0026#34;nautobot_token\u0026#34;: os.getenv(\u0026#34;NAUTBOT_TOKEN\u0026#34;), \u0026#34;filter_parameters\u0026#34;: {\u0026#34;location\u0026#34;: location}, \u0026#34;ssl_verify\u0026#34;: False, }, }, ) print(f\u0026#34;Hosts found: {len(my_nornir.inventory.hosts)}\u0026#34;) # Print out the keys for the inventory print(my_nornir.inventory.hosts.keys()) if __name__ == \u0026#34;__main__\u0026#34;: main() When you run this script you will get the following output showing the length of the devices and the key list (as defined on line 43).\nSet Environment Variables for Network Devicesexport NET_USERNAME=\u0026#34;my_username\u0026#34; export NET_PASSWORD=\u0026#34;my_password\u0026#34; This just set the environment variables of NET_USERNAME and NET_PASSWORD. You can confirm this by executing:\nVerify environment variables are setenv | grep NET_ Which will provide the output:\nOutput of env | grep NET_❯ env | grep NET_ NET_USERNAME=my_username NET_PASSWORD=my_password Environment File Option\nI did previously did a blog post at https://josh-v.com/nautobot-environment-file/ that went into defining environment files and how you would load the data into the environment.\nTransform Function - Get Username # Now that there are usernames and passwords in the environment, let's look at how to add those to your Nornir inventory via the transform function. The first part is defining the transform function. Here I will add a function within the same Python file (1) defined outside of the `main()` function. You may want to look at putting all of the transform functions in a separate file as you get going. def update_credentials(host: Host): \u0026#34;\u0026#34;\u0026#34;Update the credentials for the host. Args: host (Host): The host object to update. \u0026#34;\u0026#34;\u0026#34; host.username = os.getenv(\u0026#34;NET_USERNAME\u0026#34;) host.password = os.getenv(\u0026#34;NET_PASSWORD\u0026#34;) The function is more lines of documentation than the code itself. Assigning host.username to the environment variable for NET_USERNAME and host.password to the environment variable for NET_PASSWORD. You may want to put a little more logic somewhere to make sure that these are defined, since the os.getenv() method returns None if it is not found.\nUpdate Pyproject.toml #The second step in this process is to add a section in the pyproject.toml file. This will be needed to help register the function to Nornir. Lines 13 \u0026amp; 14 are what are required for the Nornir plugin registration.\npyproject.toml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [tool.poetry] name = \u0026#34;sandbox-python\u0026#34; version = \u0026#34;0.1.0\u0026#34; description = \u0026#34;\u0026#34; authors = [\u0026#34;Josh VanDeraa \u0026lt;\u0026gt;\u0026#34;] readme = \u0026#34;README.md\u0026#34; [tool.poetry.dependencies] python = \u0026#34;^3.9\u0026#34; nornir = \u0026#34;^3.4.1\u0026#34; nornir-nautobot = \u0026#34;^3.2.0\u0026#34; [tool.poetry.plugins.\u0026#34;nornir.plugins.transform_function\u0026#34;] \u0026#34;update_credentials\u0026#34; = \u0026#34;explore-nornir-transformation:update_credentials\u0026#34; [build-system] requires = [\u0026#34;poetry-core\u0026#34;] build-backend = \u0026#34;poetry.core.masonry.api\u0026#34; Let\u0026rsquo;s take a look at the two parts on the right side of the assignment on line 14, with the string of explore-nornir-transformation:update_credentials. In the middle is the colon : that separates the two. The first half is the path to get to the function, with the second half of the string representing the function name that is in the file. So you can nest it using dotted . notation.\nRegistering The Function #The last step is to register the transform function. That is completed by adding the line below into the script/application. This just needs to be executed before InitNornir(). See line 21 in the completed demo application.\nTransformFunctionRegister.register(\u0026#34;update_credentials\u0026#34;, update_credentials) Completed Python File #Here is the completed demo file:\nexplore-nornir-transform.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 \u0026#34;\u0026#34;\u0026#34;Testing file.\u0026#34;\u0026#34;\u0026#34; import os import urllib3 from nornir import InitNornir from nornir.core.inventory import Host from nornir.core.plugins.inventory import TransformFunctionRegister # Disable InsecureRequestWarning urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def update_credentials(host: Host): \u0026#34;\u0026#34;\u0026#34;Update the credentials for the host. Args: host (Host): The host object to update. \u0026#34;\u0026#34;\u0026#34; host.username = os.getenv(\u0026#34;NET_USERNAME\u0026#34;) host.password = os.getenv(\u0026#34;NET_PASSWORD\u0026#34;) TransformFunctionRegister.register(\u0026#34;update_credentials\u0026#34;, update_credentials) def main(): \u0026#34;\u0026#34;\u0026#34;Nornir testing.\u0026#34;\u0026#34;\u0026#34; location = [\u0026#34;ORD01\u0026#34;] my_nornir = InitNornir( inventory={ \u0026#34;plugin\u0026#34;: \u0026#34;NautobotInventory\u0026#34;, \u0026#34;options\u0026#34;: { \u0026#34;nautobot_url\u0026#34;: os.getenv(\u0026#34;NAUTOBOT_URL\u0026#34;), \u0026#34;nautobot_token\u0026#34;: os.getenv(\u0026#34;NAUTBOT_TOKEN\u0026#34;), \u0026#34;filter_parameters\u0026#34;: {\u0026#34;location\u0026#34;: location}, \u0026#34;ssl_verify\u0026#34;: False, }, \u0026#34;transform_function\u0026#34;: \u0026#34;update_credentials\u0026#34;, }, ) print(f\u0026#34;Hosts found: {len(my_nornir.inventory.hosts)}\u0026#34;) # Print out the keys for the inventory print(my_nornir.inventory.hosts.keys()) host1 = my_nornir.inventory.hosts[\u0026#39;ord01-dist-01\u0026#39;] print(host1.username) print(host1.password) if __name__ == \u0026#34;__main__\u0026#34;: main() When this executes you get the following output:\nexplore-nornir-transform.py output 1 2 3 4 Hosts found: 27 dict_keys([\u0026#39;ord01-dist-01\u0026#39;, \u0026#39;ord01-edge-01\u0026#39;, \u0026#39;ord01-edge-02\u0026#39;, \u0026#39;ord01-leaf-01\u0026#39;, \u0026#39;ord01-leaf-02\u0026#39;, \u0026#39;ord01-leaf-03\u0026#39;, \u0026#39;ord01-leaf-04\u0026#39;, \u0026#39;ord01-leaf-05\u0026#39;, \u0026#39;ord01-leaf-06\u0026#39;, \u0026#39;ord01-leaf-07\u0026#39;, \u0026#39;ord01-leaf-08\u0026#39;, \u0026#39;ord01-pdu-01\u0026#39;, \u0026#39;ord01-pdu-02\u0026#39;, \u0026#39;ord01-pdu-03\u0026#39;, \u0026#39;ord01-pdu-04\u0026#39;, \u0026#39;ord01-pdu-05\u0026#39;, \u0026#39;ord01-pdu-06\u0026#39;, \u0026#39;ord01-pdu-07\u0026#39;, \u0026#39;ord01-pdu-08\u0026#39;, \u0026#39;ord01-pdu-11\u0026#39;, \u0026#39;ord01-pdu-12\u0026#39;, \u0026#39;ord01-pdu-13\u0026#39;, \u0026#39;ord01-pdu-14\u0026#39;, \u0026#39;ord01-pdu-15\u0026#39;, \u0026#39;ord01-pdu-16\u0026#39;, \u0026#39;ord01-pdu-17\u0026#39;, \u0026#39;ord01-pdu-18\u0026#39;]) my_username my_password Lines 3 \u0026amp; 4 are the print out of the username and password. This is demonstration of setting these values via transform functions and you probably should not be actually printing passwords 🔒.\nSummary #Using the Transform Function in Nornir is the recommended method for adding credentials to a Nornir script or application. This approach ensures that your secrets are managed separately from your inventory, enhancing security. By incorporating logic to integrate with a secrets vault, such as Hashicorp Vault, within the environment variable loading function, you can securely obtain the necessary credentials to connect to your devices. This method keeps your sensitive information protected while maintaining the flexibility and functionality of your Nornir applications.\nDon\u0026rsquo;t Have a Secrets Management? If you do not have a secrets management system that is programmatically available, I recommend using Hashicorp Vault. You can read more about that in my book Open Source Network Management for setting up Vault in Docker environment (amongst other open source tools).\n","date":"2024-06-04","permalink":"https://josh-v.com/nornir-transform-3/","section":"Posts","summary":"\u003cp\u003eNornir includes a function that allows for the transformation of inventory data, a feature integrated within the Nornir platform itself. The documentation for Nornir 3.0 is somewhat sparse regarding the usage of Transform functions, so I often refer to the more comprehensive \u003ca href=\"https://nornir.readthedocs.io/en/v2.5.0/howto/transforming_inventory_data.html#Modifying-hosts%27-data\" target=\"_blank\" rel=\"noreferrer\"\u003e2.5 documentation\u003c/a\u003e. According to the Nornir documentation:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eA transform function is a plugin that manipulates the inventory independently from the inventory plugin used. Useful to extend data using the environment, a secret store or similar.\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Nornir Transform Function"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/python/","section":"Categories","summary":"","title":"Python"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/vault/","section":"Categories","summary":"","title":"Vault"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/networking/","section":"Categories","summary":"","title":"Networking"},{"content":"In this post, we\u0026rsquo;ll dive into WAN design and address a common question that I was provided with in the 2000s: \u0026ldquo;My home internet costs only $35 per month. Why do we spend $xxx per month per circuit?\u0026rdquo;\nThis question is one that WAN engineers in 2024 and beyond shouldn\u0026rsquo;t have to worry about. As long as we design for the expected availability (which I discussed in a previous post), the types of circuits are no longer a major concern.\nScenario #The WAN environment we supported hosted many critical applications essential for daily business operations, including revenue processing (online sales were still in their infancy back then). Our design was \u0026ldquo;cookie cutter,\u0026rdquo; meaning all locations were identical. This approach meant using the same type of circuit for each site, limiting us to a few suppliers and reducing competition.\nThere were minor differences in hardware at each location, and we used one-time config generation templates to handle these differences, with logic built into the templates for the type of WIC installed on the router.\nWhat I Would Do Differently #Initially, I thought the only change I\u0026rsquo;d make was using a more accessible circuit database. However, as I wrote, I realized another improvement: updating the template generator. Let\u0026rsquo;s explore these two changes.\nSolution: Circuit Warehouse/Database #First, I would use a SOT (Source of Truth) like Nautobot as a Circuit Warehouse/Database. Nautobot can track essential details like circuit ID, provider, type of circuit, and the connection point. You can see an example in the Nautobot demo, where a circuit is connected to sin01-edge-01 on interface Ethernet16/1.\nWith this information, I could use Python or Ansible to generate interface configurations. Nautobot\u0026rsquo;s API provides variables for IP addressing, interface type, port speed, shaping configuration, and more. This flexibility allows for different circuit types and service providers across the environment. Plus, with Nautobot\u0026rsquo;s data readily available, operational efficiency improves. Tools like Nautobot ChatOps can bring this information to your help desk team via MS Teams, WebEx, Slack, or Mattermost.\nAt the time, our Telecom Expense Management (TEM) system focused on expenses rather than data accessibility. Even if it had API capabilities, I wasn\u0026rsquo;t aware of APIs back then, so I wouldn\u0026rsquo;t have known how to extract the data. Learning how to use REST APIs is well worth the time investment.\nUpdate the Template Generator to Run On Demand #The generator script I inherited was written in Perl and tied to a spreadsheet that needed updating before execution. While Perl isn\u0026rsquo;t inherently bad, it was challenging for me to understand at the time.\nI should have updated this command-line utility to accept CLI inputs for single-device configuration generation. Python 2.6 had just been released, but I didn\u0026rsquo;t encounter Python until 2015.\nTemplating Configurations Today #Today, I\u0026rsquo;d use Python Jinja for configuration templates. Jinja integrates well with Python, Ansible, Nautobot Golden Configuration, and many other network automation projects, making it the best choice for templated configuration generation.\nSummary #Change is constant, and my approach to WAN design has evolved. Previously, we aimed for native Ethernet service everywhere from a single provider. Now, it\u0026rsquo;s about finding the right provider for each location. Using a Circuit Warehouse/Database like Nautobot to manage site-level details ensures the configuration works seamlessly at each site.\n","date":"2024-05-31","permalink":"https://josh-v.com/redux-wan-design/","section":"Posts","summary":"\u003cp\u003eIn this post, we\u0026rsquo;ll dive into WAN design and address a common question that I was provided with in the 2000s: \u0026ldquo;My home internet costs only $35 per month. Why do we spend $xxx per month per circuit?\u0026rdquo;\u003c/p\u003e","title":"Redux: WAN Design"},{"content":"I have been looking at migrating over to the Starship shell for a little while. The allure of running a rust shell prompt that gives me a ton of information is what I look for in a shell prompt. Such as the prompt below:\njoshv in 🌐 my_device in nautobot on  u/jvanderaa-update_install_home_doc is 📦 v2.2.5b1 via 🐍 v3.11.9 (nautobot-py3.11) The default installation however did not get the same behavior as my previous Oh My Zsh set up with the zsh-autocompletions and zsh-syntaxhighlighting. Whenever I would hit the up arrow key, the system would cycle through the commands as comes default with zsh/bash. But I was looking for subcommand scrolling. Such as the following command sequence.\n1 2 3 poetry init ls systemctl status nautobot When I type poetry on the 4th command, I wanted the search to find poetry init immediately as my last used poetry command. To be honest, I\u0026rsquo;m not sure where the feature came from within my previous set up of Zsh, Oh my zsh, and the powerlevel10k setup that I was using. But it is part of my zen experience on a shell.\nChatGPT to the Rescue #I was about to abandon my Starship prompt and just get back to installing Oh my zsh and powerlevel10k when I decided to ask ChatGPT:\nOk, the Starship prompt is not getting me what I would like. I may just go back to oh my zsh to get the proper suggestions. Tell me if I could do this with starship.\nCommand 1: poetry env use 3.11.4\nCommand 2: ls\nCommand 3: systemctl status\nNow for command 4, when I start typing poetry I want the up arrow key to suggest command 1. Right now it moves up to Command 4.\nFrom there I got the response that inspired this post and to dive more into bindkeys. The suggestion came back to add the following to my ~/.zshrc file:\n# Custom history search bindings bindkey \u0026#39;^[[A\u0026#39; history-search-backward bindkey \u0026#39;^[[B\u0026#39; history-search-forward With the bindkeys in place and testing, it gave me exactly what I was looking for out of my history searching on the command line. A quick reload of the shell environment and bam it took care of exactly what I was looking for.\nSummary #This post has been more for my own documentation for when I inevitably rebuild a system somewhere and need to find what I have done in the past. I\u0026rsquo;m hopeful that this post may help out others as well, as this is the reason that I put things out in a blog format instead of just keeping an internal wiki. Thanks for the read!\n-Josh\n","date":"2024-05-29","permalink":"https://josh-v.com/bindkey-search/","section":"Posts","summary":"\u003cp\u003eI have been looking at migrating over to the \u003ca href=\"https://starship.rs\" target=\"_blank\" rel=\"noreferrer\"\u003eStarship shell\u003c/a\u003e for a little while. The allure of running a rust shell prompt that gives me a ton of information is what I look for in a shell prompt. Such as the prompt below:\u003c/p\u003e\n\u003cdiv class=\"code-block-wrapper\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ejoshv in 🌐 my_device in nautobot on  u/jvanderaa-update_install_home_doc is 📦 v2.2.5b1 via 🐍 v3.11.9 (nautobot-py3.11)\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp\u003eThe default installation however did not get the same behavior as my previous Oh My Zsh set up with the zsh-autocompletions and zsh-syntaxhighlighting. Whenever I would hit the up arrow key, the system would cycle through the commands as comes default with zsh/bash. But I was looking for subcommand scrolling. Such as the following command sequence.\u003c/p\u003e","title":"Bindkey For Autocompletion"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/chatgpt/","section":"Categories","summary":"","title":"Chatgpt"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/blog/","section":"Categories","summary":"","title":"Blog"},{"content":"So, if you are a returning user you notice something that is a little bit different. I have migrated my blog site to a new site generator and format. I am now using [mkdocs-material] blog them that was introduced in late 2022 to the platform. The migration was not too painful to make, but I\u0026rsquo;m feeling in a good place about it at this point. The blog has been on a journey so far. It started off with a Jekyll themed site which I liked. Then I decided to move onto something that has a little more development. So I moved into the land of Hugo.\nWhy the Change #The idea of changing first crept into my mind because I was having a mild panic attack after attempting to publish my latest post and GitHub Actions was failing the build process. It turned out that the processing time to build the Hugo blog was just taking longer than the timeout that was default at 30 seconds. In fairness to me, this didn\u0026rsquo;t even cross my mind that it would be an issue since on my home system that same build process would take under a second. So in the troubleshooting I thought, what if I moved my blog to mkdocs?\nRecently I have been writing a fair bit of documentation updates to the Nautobot documentation and have found the additional features and capabilities to the format quite enticing. That, along with a general easier method to implement admonitions over having to use Hugo shortcodes for everything. With those in mind, I decided to move forward with it.\nSorry RSS Feed In making the migration, I didn\u0026rsquo;t properly exclude sections of the blog that shouldn\u0026rsquo;t go into the RSS feed. That has been cleared up going forward. Hopefully that will settle down as we go. I still need to investigate if the RSS system has a method to migrate a site to a new feed URL without it just being a fresh take. Research later.\nInteresting Challenges? #There were a few challenges along the way. Making a change in technology almost certainly has some challenges.\nGitHub Actions Failures #I worked a little bit with ChatGPT along the way to help. So of course there were a few things that I had to work through. The first challenge working through was that I set up two CI files, one for testing to make sure the build would work. The second was to do the actual deployment. These mostly should be the same. The deployment CI action was failing. I ended up working my way and with the help from another mkdocs blog - Copdips.com I was able to get the deployment CI system working. Using their deployment tests, it deploys flawless.\nRSS Feed #The RSS feed tool has a different URL for the feed. While not a huge subscriber base (especially when I look at the numbers on Feedly), I want to be able to allow those that have set it up to not have a disruption. I have a copy of the feed file made from the existing file. This is a hack that I had used on the Hugo blog as well.\nEasiest Getting Started #I attempted a couple of times to follow a site that had the start of the blogging set up, but I kept getting some roadblocks. Whether it was that my post URLs were not formatting the way that I wanted them or something was showing up where they shouldn\u0026rsquo;t. I eventually followed the setting up a blog setup page. Once I did that from scratch there, I was off to the races. If you want to start your own blog, and I encourage you to do so, then I would recommend starting with that page. It gets things moving quite quickly.\nOutstanding ToDos #Figure out the commenting System #I need to figure out the commenting system and how to migrate over to a new site. This may require a re-do of the config option yet. I\u0026rsquo;ve done minimal research thus far. So this may be next.\nCheck through some of the old posts #Working through updating my old posts is on the list. I think I got most of the pieces that I need. So this may be done. Just not sure yet. Working on a few things for work that are taking priority on that.\nLook into building a command #The last reason why I moved was that mkdocs is Python :fontawesome-brands-python:. At this point in my career with automation tools and such, I\u0026rsquo;m on the Python fan page. More to come on this. So I\u0026rsquo;m looking forward to seeing what I can do to extend the capabilities as I need them here.\nAlso a contributing factor is that @squidfunk has what looks like a pretty successful business in supporting mkdocs-material.\nLet me know your thoughts.\n-Josh\n","date":"2024-05-28","permalink":"https://josh-v.com/to-mkdocs/","section":"Posts","summary":"\u003cp\u003eSo, if you are a returning user you notice something that is a little bit different. I have migrated my blog site to a new site generator and format. I am now using [mkdocs-material] blog them that was introduced in late 2022 to the platform. The migration was not too painful to make, but I\u0026rsquo;m feeling in a good place about it at this point. The blog has been on a journey so far. It started off with a Jekyll themed site which I liked. Then I decided to move onto something that has a little more development. So I moved into the land of Hugo.\u003c/p\u003e","title":"Migration to MkDocs Material Blog Theme!"},{"content":"Today I\u0026rsquo;m going to dive into my getting started with network automation, and perhaps my first successful automation. There are definitely some things that I would re-do and complete differently, and some things that I consider a success.\nI\u0026rsquo;m working on a new series within my blog, about how I would look to have done things differently than I had done before, with the tooling and knowledge that I now have, years later. This is the first in the series.\nScenario #The issue at hand was there was an issue with a particular firewall system that was causing various connection issues to occur within the Microsoft Office suite of communications. There were some tables that were tracking Microsoft RPC connections through the firewall that were filling up. There were no log messages indicating that this was happening. Nor were there any metrics available via SNMP at the time.\n![RPC connections through firewall](fw_traffic.png) I forget the details about how it was diagnosed as an issue and made its way to the firewall as the culprit. But that is not important. How The Issue Was Solved #The issue was initially solved by a crawl, walk, walk faster initiative. I would typically go down the path of crawl, walk, run path, but in this case as I look back at things, I would say that there are some other methods that I could have taken to get things further. But was not necessary.\nFirst thing was figuring out how to clear the tables, which was done with a quick command. This was being done whenever there was a report of the tables filling up, which was happening actually relatively frequently. On top of having this information at hand, there was an escalation to the firewall vendor to help alleviate the issue in the firmware.\nThe next step was creating an HPNA script to be able to be executed by our second level support team whenever the issue was detected or had a call in to clear the tables. This was a start, but still not quite what was looking for.\nThe Automation Used #I had been working on developing my Python automation skills at this time. So as a learning opportunity I took the initiative to look at what could I do with a Python script. In this learning time, I had not been so well connected to the community of network automation and thus was not aware of the awesome Netmiko library at the time. So what was I doing? I was essentially recreating it as an internal package. That will be covered more in the next section.\nSo the script was able to log into the firewall. It then read the command output and using regex to parse the output, looking for the current counter value of the RPC tables. When the counter was maxed out, the script would then clear the tables. The script was then set to run on a cron schedule of every 30 minutes. And at that point, things were good to go until the bug could be solved in a main line code.\nWhy Not Upgrade? #The impact was immediate. The anticipated amount of time that it would take the vendor to diagnose and re-create the issue, engineer a patch, and generate an engineering special patch (before being incorporated into the main code base) would take some time. On top of that, the comfort level of running an \u0026ldquo;Engineering Special\u0026rdquo; patch on the firewalls was not that comfortable. If that was the only fix, then that would be a route to go. But there was another option. Use the automation that was built.\nWhat Would I Do Now? #So many years later, knowing what I know now, there are definitely some things that I would do differently. Many are around working with existing tooling (which did exist in a form back then) or contributing back to open source.\nWhat I Would Do The Same #What would I do the same. First the iterative approach was a good path. I would absolutely look to do the same thing again there.\nThe second piece that I am proud that I worked within was the use of the corporate security password management. There was no need to store the credentials to log into devices.\nLastly, I\u0026rsquo;m glad that I used Python to complete the activity. It was something that I could iterate on, had a good time building the script out, and was effective.\nPython Script #First, I wrote everything in Python 2.7. This was when there was still a debate between Python 2.7 and 3.4/3.5. I would absolutely change and move along with using Python 3.x, whichever is the latest as I get started.\nThe reason that Python 2.7 was chosen at the time was to be able to have something that was compatible with running on network devices, so that as we continued our development, that we could leverage on box Python. This I should have just moved forward to 3.x.\nToday Python is moving the version forward rapidly in my opinion. So it is good to plan that if you are just getting started, start with the latest version of Python available. This will help keep longevity in the apps that are developed.\nNetmiko #At the time I was not aware of the network automation community. I ended up learning the same things that the community and the Kirk Byers Netmiko project had worked through. I was working through how to interact with network devices through the Paramiko shell, exactly what had been done before. So I absolutely would have changed to just using the Netmiko library. That is the first thing that I would have done for sure.\nTextFSM #Next up in what I would do differently is that coupled with using Netmiko, I would use TextFSM to handle the command regex parsing. I am doubting that there would have been an NTC-Template available for the command, but I would look to create a template based on the command output. At that point when the template was created, I would then look to submit a PR into the NTC-Templates library so that others would be able to use the same template.\nNote that Kirk has an excellent blog post about how to use NTC-Templates right in with Netmiko. Previous to the Netmiko 2.0 release, you would need to get the command output and then parse the output separately. These are now all in one with the Netmiko library.\nMonitoring #The last piece with having the script now, I would look to enhance the monitoring of it, to know how often that the tables were filling up, and what the pace was. There are still two methods here, of one generating a log message to send into a logging system. Or to set up a graphing capability using Telegraf, Prometheus, and Grafana. I would look at implementing the graphing solution that I wrote up several years ago on the NTC Blog about Monitoring VPN infrastructure with Netmiko, NTC-Templates, and a Time Series Database\nSummary #There are definitely some things that I would look to do a small bit differently. But the thing that I like most is that I was able to apply some recently developing skills at the time - Python, to directly get a business outcome. It may have been seen as a break/fix just fixing things, but it was a band aid that was needed, and helped save my personal work/life balance (not having to clear the tables often manually). Network Automation is an ever evolving field. There will be continued maintenance of what is built.\nOverall, the journey is about continuing to learn. Learning is one of the important aspects of the DevOps culture. I\u0026rsquo;m hopeful that this may help others on the journey to either learn from my past (would not call them mistakes), or inspire other thoughts of your own. Let me know what you think.\n","date":"2024-05-21","permalink":"https://josh-v.com/firewall-tables/","section":"Posts","summary":"\u003cp\u003eToday I\u0026rsquo;m going to dive into my getting started with network automation, and perhaps my first successful automation. There are definitely some things that I would re-do and complete differently, and some things that I consider a success.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m working on a new series within my blog, about how I would look to have done things differently than I had done before, with the tooling and knowledge that I now have, years later. This is the first in the series.\u003c/p\u003e","title":"Automation Redux: Firewall Tables"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/documentation/","section":"Categories","summary":"","title":"Documentation"},{"content":"One of my favorite features of Nautobot that may not be well known is the capability to put a button on pages that take you to other locations. This can be helpful when lining up the source of truth as that first place that you go, the idea of adding custom links will just help to enforce that as the first place to go. When you look at the idea of a source of truth to help feed other systems, you start to see the topology like below.\nThe documentation for creating your own custom links is here: https://docs.nautobot.com/projects/core/en/stable/user-guide/platform-functionality/customlink/\nCustom Link Build #Custom Links are built in the Nautobot UI under the Extensibility menu. Let\u0026rsquo;s build a basic button on the demo instance of Nautobot. Let\u0026rsquo;s build a link for Locations that will point to the Nautobot docs page as an example. Some ideas for actual implementations include links to site dashboards in your monitoring tool, or a link on a circuit to the portal page, or a link to CI data inside of your ITSM tool for a device.\nNavigate and log into https://demo.nautobot.com (Log in credentials are on the site, as of now demo/nautobot) On the left hand navigation menu, select EXTENSIBILITY and then select Custom Links The site by default does not have any links, there may be a few that demo users create themselves. Select +Add on the upper right Fill out the form with the following: Field Value Description Content Type dcim | location What Nautobot data type this applies to. Name Site Link The name of the custom link. Text Nautobot Docs What the button should say. URL https://docs.nautobot.com What the link is sending to. Weight 100 This helps tweak where the link will appear in relation to other links. Group Name If you want to group links together. Button Class Info (Aqua) This colors the button for the link. New Window Checked Opens the link in a new tab/window. Click on Create on the bottom. This will create the link. Now when you navigate to a Location, you will now see a button on the upper right that will say Nautobot Docs that will take you to the Nautobot documentation pages.\nAdvanced Link Creation #One of the awesome things that comes along with the Custom Links is the ability to put some logic into the text that you put there, such as only show the button if it is a particular device type, or manufacturer. This may also play in with systems that are part of a controller based system. Perhaps a link to Arista CloudVision if the device is an Arista device. I have done links to Grafana dashboards using the Modern Telemetry system that we have deployed, only if the device is of a particular device type that would be created to be in the monitoring solution.\nLet\u0026rsquo;s make two more links using the same steps above, but with the custom logic of a link to CloudVision when the device manufacturer is Arista. If the device manufacturer is Cisco then link to the Cisco controller. In both instances for the demo, it will be a link to the manufacturer\u0026rsquo;s webpage. The custom text we will want is the following (will be a single line in the text of the button):\nArista CloudVision Custom Link #{% if obj.device_type.manufacturer.name == \u0026#34;Arista\u0026#34; %} CloudVision {% endif %} Field Value Description Content Type dcim | device What Nautobot data type this applies to. Name Arista CloudVision The name of the custom link. Text {% if obj.device_type.manufacturer.name == \u0026ldquo;Arista\u0026rdquo; %}CloudVision{% endif %} What the button should say. URL https://www.arista.com/en/products/eos/eos-cloudvision What the link is sending to. Weight 100 This helps tweak where the link will appear in relation to other links. Group Name If you want to group links together. Button Class Warning (Orange) This colors the button for the link. New Window Checked Opens the link in a new tab/window. Cisco Custom Link #{% if obj.device_type.manufacturer.name == \u0026#34;Cisco\u0026#34; %} Cisco {% endif %} Field Value Description Content Type dcim | device What Nautobot data type this applies to. Name Cisco The name of the custom link. Text {% if obj.device_type.manufacturer.name == \u0026ldquo;Cisco\u0026rdquo; %}Cisco{% endif %} What the button should say. URL https://www.cisco.com What the link is sending to. Weight 100 This helps tweak where the link will appear in relation to other links. Group Name If you want to group links together. Button Class Warning (Orange) This colors the button for the link. New Window Checked Opens the link in a new tab/window. Demonstration #Now when you navigate to devices on Nautobot, when the device type manufacturer is present, you have new buttons that are available. Taking a look at an Arista device you now see the CloudVision button the top right.\nAnd when you navigate to a Cisco device as expected you have the Cisco button.\nSummary #Nautobot\u0026rsquo;s Custom Links are a valuable part of building out the Network Automation Source of Truth. Knowing that there are going to be other systems, it is good to link to those other systems to help speed access to the particular device types. This is one more thing that will help drive the source of truth concept that is valuable inside of an organization, by allowing it to be a starting point of what the system should look like, and quickly accessing other data points that would be of value. I personally am a big fan of the Custom Links and have been driving them for a while. Custom Links are available in all versions of Nautobot.\nThanks for reading!\n-Josh\n","date":"2024-04-28","permalink":"https://josh-v.com/nautobot-custom-link/","section":"Posts","summary":"\u003cp\u003eOne of my favorite features of Nautobot that may not be well known is the capability to put a button on pages that take you to other locations. This can be helpful when lining up the source of truth as that first place that you go, the idea of adding custom links will just help to enforce that as the first place to go. When you look at the idea of a source of truth to help feed other systems, you start to see the topology like below.\u003c/p\u003e\n\u003cp\u003e\u003cfigure\u003e\n  \u003cimg src=\"image-8.png\" alt=\"Nautobot Source of Truth for Other Systems\" class=\"mx-auto my-0 rounded-md\"\u003e\u003c/figure\u003e\u003c/p\u003e","title":"Nautobot: Custom Links"},{"content":"Just recently released at the beginning of 2024 is a project that I am super excited to see in the open source by Network to Code. This is the Nautobot App cookiecutter template. This may already be the biggest thing to become available for Network Automation in 2024. I know, its fresh at this point in the year, but this is something that is going to make getting started with your own Nautobot Application so much quicker.\nCookieCutter #First, a real quick summary and link to more documentation of what is a CookieCutter? Well, just like in making cookies with fancy designs, this is a template that will generate the entire project layout for you for a Nautobot App. Using Python CookieCutter, you tell Python to build a directory structure/layout according to the template defined.\nNautobot #The first stop for getting started in your Network Automation journey should be to get Nautobot up and running. Get a Linux Virtual Machine, preferably Ubuntu or RHEL/RHEL derivative. From there you can follow the Nautobot installation instructions for your flavor of Linux. You should look to get a few users set up and then you can work to get your existing environment into Nautobot via the Onboarding Plugin followed by Network Importer to get the existing environment into Nautobot. This is the easiest path for getting your network ready to go. Or if you do not have a supported device type from the Network Importer process, then I have a process that leverages Ansible Facts to get the information from the network into Nautobot.\nNext step now that you have data and an inventory? My recommendation is to build a Nautobot App that you can use to install your custom code and data into Nautobot quickly and efficiently.\nWhy the Nautobot App #So why would I be looking at adding my own Nautobot App? First, it is not to recreate something that already exists. So it is not to write code to audit your network configuration. There is already a proven Nautobot App that does this for you, Nautobot Golden Config. Also, it is not to do something that you could contribute back to an open source project. It is meant to help you build, enhance, and enforce your organization\u0026rsquo;s business logic.\nNautobot Jobs are a perfect place to centralize your Python scripts that may be on a single developers workstation. To that end, if there is a script that executes business logic, or solves a problem that can then be used by a help desk team member or an application team, this will help to make your organization more productive.\nAlso, in an earlier post on Custom Validators I covered how to apply custom validators from Nautobot on the data. In particular that post showed how you can interact with other 3rd party systems to Nautobot in order to do validation based on other services.\nSummary and Next Posts #Upcoming posts are going to get into the whats next. Where I will look at the following questions and more.\nExploring the layout of the baked cookie How do you install this application into your environment? How to get started with Nautobot Jobs Nautobot Custom Validators Creating Your Own Views What else would you like to see as you get started with Nautobot and your own app?\n-Josh\n","date":"2024-01-11","permalink":"https://josh-v.com/nautobot-app-cookie/","section":"Posts","summary":"\u003cp\u003eJust recently released at the beginning of 2024 is a project that I am super excited to see in the open source by Network to Code. This is the Nautobot App cookiecutter template. This may already be the biggest thing to become available for Network Automation in 2024. I know, its fresh at this point in the year, but this is something that is going to make getting started with your own Nautobot Application so much quicker.\u003c/p\u003e","title":"Nautobot App Baking Cookies"},{"content":"","date":null,"permalink":"https://josh-v.com/categories/programming/","section":"Categories","summary":"","title":"Programming"},{"content":"The year of 2023 I think may have had some of the biggest leaps in the Network Automation capabilities that are being delivered by some of the best in the business. With Nautobot\u0026rsquo;s Golden Config App adding the ability to complete configuration remediation and Ansible release Event Driven Ansible, there are a couple of powerful tools to help you with your Network Automation. And all with a great new conference addition specific to Network Automation.\nNautobot Golden Config - Configuration Remediation #On the surface configuration remediation may not sound like a big deal. But I was astonished when the team that worked on the feature within Nautobot Golden Config demo\u0026rsquo;d the features and capabilities internally before the webinar releasing it. Initially, ok this is going to be pushing configuration. To me that is generally a solved problem, and wouldn\u0026rsquo;t be a big deal.\nWhat the tool brings to the automation capabilities the ability to add an approval workflow to the configuration remediation effort. This is a big time process improvement in its own. On top of this, you are able to correlate which devices are associated to the configuration plan. And this all comes with the templating capabilities of the data that is in your SOT. Check out the YouTube Video for a deeper introduction.\nThis is coming from a project that I am employed at. I still believe in the statements of this being game changing for configuration management. I would like to think that I am able to still be impartial, but I was truly impressed seeing this. Event Driven Ansible #Next up we really started to see a lot more Event Driven Ansible (EDA). Technically announced in 2022, but it really started to gain conversation a lot more in 2023. EDA brings the ease of getting started with Ansible to Events, which are things that happen in the environment. What does this mean? Well, out of the box one of the event driven capabilities with EDA is to be a webhook receiver. This means that you can set up EDA to listen for an incoming webhook, and then launch an Ansible playbook based on conditions received in the webhook. This is all configured via rule books.\nOther capabilities natively as part of the system include the capability to listen to Kafka topics. Kafka is one of the leading pub/sub messaging systems that allow for providing messages to various systems. So you can have EDA subscribe to a particular topic on Kafka, and then kick off an Ansible Playbook from the message, again based on the conditions that are in the rule book.\nNetwork Automation Forum - autocon0 #This is not going to be a review, but a kudos and thank you to those at Network Automation Forum that brought autocon0 to being. This was a very well attended conference in the fall of 2023, and with a good number of attendees, it shows that the topic of Network Automation is still hot.\nSummary #2023 was an awesome year for Network Automation, and I foresee even more coming in 2024. There will be even more built on top of what currently exists in open source, and I foresee even more new capabilities being brought and talked about all together.\n-Josh\n","date":"2023-12-15","permalink":"https://josh-v.com/2023-automation-review-top-3/","section":"Posts","summary":"\u003cp\u003eThe year of 2023 I think may have had some of the biggest leaps in the Network Automation capabilities that are being delivered by some of the best in the business. With Nautobot\u0026rsquo;s Golden Config App adding the ability to complete configuration remediation and Ansible release Event Driven Ansible, there are a couple of powerful tools to help you with your Network Automation. And all with a great new conference addition specific to Network Automation.\u003c/p\u003e","title":"2023 Automation Review: Top 3"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/ansible/","section":"Tags","summary":"","title":"Ansible"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/communications/","section":"Tags","summary":"","title":"Communications"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/slack/","section":"Tags","summary":"","title":"Slack"},{"content":"Recently Slack started introducing a new UI that is bringing back the importance of knowing keyboard shortcuts. Keyboard shortcuts within Slack are immensely helpful in my day to day, and it is essential for me being able to keep up with what is going on within my organization and some of the other 20 Slack organizations that I have joined. So much so, I don\u0026rsquo;t believe that once I am in the Slack UI on my machines, I am ever touching the mouse to get from one place to another. About the only time I may need to use the mouse is to scroll back in the thread, so I\u0026rsquo;m not even clicking when using the mouse.\nThe keyboard shortcuts that I will be describing here will be demonstrated with CMD + \u0026lt;keyboard key\u0026gt;. These are for the Mac keyboard. If the Linux keyboard shortcuts of using the control key, should be the equivalent on the Windows platform as I understand it. If I am off on these keys, please let me know in the comments. My Common Keyboard Shortcuts #Slack makes the keyboard shortcuts available via CMD + /. This gives you the full list of commands that are available.\nNavigating To Channels - Unread Channels #In my typical scenario I\u0026rsquo;m navigating between channels within an organization. The first thing that I\u0026rsquo;m doing is using CMD + K to find my unread channels. It gives a really nice list of the channels that are unread right at the top and in bold. You can then arrow down to those unread channel that you wish to look at and then press enter in order to get to the channel.\nHere this has everything in an unread state, but you can see where you can start to navigate. If there were some channels that had not been read yet, they would appear here in bold.\nNavigating To Channels - Just Getting to Channel Name #The next step if you need to get to a particular channel, I\u0026rsquo;m following the similar start to the unread channels. I\u0026rsquo;m using CMD + k to bring up the window again, but now the default is a search bar. This now allows you to just start typing. This is where I type the name of the channel that I\u0026rsquo;m looking for and then selecting it with the enter key again once it is highlighted.\nNavigating to a Direct Messages (DMs) #The direct message interface is a little different on the channels. If I\u0026rsquo;m looking to create a new DM conversation with someone, I\u0026rsquo;m using the combination of CMD + Shift + k to get to the DM window. This will then provide you the ability to type various contacts to get to the DM that you wish to complete.\nThis now brings up one of the newest quirks of the new Slack interface. You are taken to a whole new view. Once I am complete with my DM session that I started, I\u0026rsquo;m now trying to get back to the home view. On the Mac side of things this is accomplished with Control + 1 to get to that view. The Linux/Windows side is a bit more complicated. There you need to use Control + Shift + 1 to get to the home view.\nWorkspace Switching #This is another quirky experience that we will dive into. The new UI has hidden the workspace switcher by default. If you wish to have the workspace switcher open for whatever scenario (like you just like them, or you have too many to use keyboard shortcuts), you can do so on Mac with CMD + Shift + s and on the Linux/Windows world with Control + Shift + s. This will allow you to change workspaces via the mouse again.\nBut this post is about my keyboard shortcuts. The workspace switching capability has some good shortcuts. This can be accomplished using CMD + # where # is the workspace number, similar to a tab experience on your web browsers. Here you can only go to 9 workspaces before you need to use a mouse, so this is where re-ordering workspaces is important.\nSummary #Keyboard shortcuts are what empowers me to move through the day and keep up quite quickly in the Slack workspaces these days. I hope that some of this information may help you as well. Are there some other keyboard shortcuts that you are using in your day to day interaction with Slack? Start a conversation in the discussions.\nJosh\n","date":"2023-11-18","permalink":"https://josh-v.com/slack-power-keyboard/","section":"Posts","summary":"\u003cp\u003eRecently Slack started introducing a new UI that is bringing back the importance of knowing keyboard shortcuts. Keyboard shortcuts within Slack are immensely helpful in my day to day, and it is essential for me being able to keep up with what is going on within my organization and some of the other 20 Slack organizations that I have joined. So much so, I don\u0026rsquo;t believe that once I am in the Slack UI on my machines, I am ever touching the mouse to get from one place to another. About the only time I may need to use the mouse is to scroll back in the thread, so I\u0026rsquo;m not even clicking when using the mouse.\u003c/p\u003e","title":"Slack Power Keyboard"},{"content":"In my home environment I am leveraging Nautobot as my source of truth. This is for the network, which is probably not all that interesting in my home environment, and my virtual machines. Why am I tracking my virtual machines in Nautobot? Simple, to help automate them. I think that this is a clever methodology to help use tags and to get automation working within the environment. This same type of thing may be applicable to your network environment as well.\nMy VM Environment #I\u0026rsquo;m still in the process at the moment of having to build my virtual machines a bit manual. I hope to automate this some more in the future yet. I\u0026rsquo;m leveraging an open source hypervisor that is running on several NUC class hosts that make up the cluster. These are running in HA mode, so if one of the hosts go down, then I should have another host that will pick up the slack and take care of running the virtual machine.\nNautobot Set Up #Within Nautobot I am adding my VMs as I add them onto the host. This is what I will automate and flip around. I am setting up the system as though I were building the VM from Nautobot, and then that goes into the hypervisor system.\nNext up, I use tags to define what application systems that are going to be installed onto the environment. So I am effectively using Tags from Nautobot to create the groups for Ansible.\nThe first set of tags that I have include:\napp=node_exporter app=plex app=telegraf As I work through my virtual machines and devices, I now have the capability to run Ansible playbooks based on the tags. So on the virtual machines that I want to deploy the Telegraf agent to send metrics to my TSDBs, I use the tag of app-telegraf. When running the playbook, I have confidence that the machines that I want to have Telegraf will in fact be there.\nApplication to Networks #How would I look to apply this process to networking? I would absolutely look to have this same thing set up for tags about what devices need to be in what system. Such as the following tags:\nMonitoring=SystemA Monitoring=SystemB AAA=RadiusSystemA AAA=TacacsSystemA When I make these tags I also change the slug away from the default value. Changing = which will be no space/character in the slugify to be a __ double underscore. By leveraging tags you can filter for devices/virtual machines and allow for customization as of the automation that is being deployed. So that you can define in your automation that devices should all be part of a monitoring system. But what if you want to take the device out of monitoring for an extended period of time? Well, you can just change the tag. Or if you want to apply a custom policy, apply a tag to the object and then in the automation check to see if the tag exists.\nNext Step: Automation #The next step for me will be to find the time to set the appropriate WebHook/JobHook for when a device gets updated/created in Nautobot to update the Ansible AWX Inventory that I have. I will likely go to Nautobot JobHook to be able to send updates only when tags are applied. Why JobHook? I can add some logic into the path to be able to handle should there shouldn\u0026rsquo;t there be an update based on the object update.\nSummary #Tags are an awesome and often forgotten capability with a SOT tool like Nautobot. You can leverage tags quite a bit to help control environments and the data that is being added/updated inside of Nautobot. Anything else that you use tags for? What other ideas do you have? Let me know in the comments.\nNeed a step by step guide on getting started with open source network management tools? Check out my book on LeanPub or on Amazon.\nJosh\n","date":"2023-09-26","permalink":"https://josh-v.com/nautobot-how-i-use-tags-for-vms/","section":"Posts","summary":"\u003cp\u003eIn my home environment I am leveraging Nautobot as my source of truth. This is for the network, which is probably not all that interesting in my home environment, and my virtual machines. Why am I tracking my virtual machines in Nautobot? Simple, to help automate them. I think that this is a clever methodology to help use tags and to get automation working within the environment. This same type of thing may be applicable to your network environment as well.\u003c/p\u003e","title":"Nautobot: How I Use Tags For VMs"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/tags/","section":"Tags","summary":"","title":"Tags"},{"content":"One of my hot topics in my past that I haven\u0026rsquo;t seen written about often is the calculation of WAN availability and what the design is built for. There is often the number of 9\u0026rsquo;s whether that is 5 9s or 3 9s or otherwise, where do you start? Well, in the past the post by EventHelix.com outlines system reliability. It talks about designing systems in parallel and in serial. What does that mean. Well, I am going to take the system availability and bring it into the Wide Area Network, which really could be brought to any environment that has a system uptime requirement as a way to calculate and validate the dollars that you are requesting. I am hoping that this will help you to be able to answer questions such as \u0026ldquo;What if we added another service provider?\u0026rdquo; or \u0026ldquo;What if we changed out hardware for a smaller/larger hardware choice?\u0026rdquo;.\nThe Nines #The first topic is to take a look at exactly what it means from a design availability perspective. When talking about availability the nines look like the following of availability from 525,600 minutes in a year (365 x 24 x 60):\nAvailability Downtime 99% 5,256 minutes (3.65 days) 99.9% 525.6 minutes 99.99% 52.6 minutes 99.999% 5.26 minutes As you can see from the chart, the more nines of availability that you must target, the much more aggressive amount of downtime that is acceptable. Many data centers have had a target of 5 nines of availability for many years, and that target may always be changing as well.\nHistorically in the WAN space you may get asked to design a WAN, validate the current design, or be asked what it would look like if you changed the design. By using a calculation of system availability you can then tie the WAN design back to real figures. So how do I build a design? Let\u0026rsquo;s start by taking a look at a few of the components to do this.\nCalculating Systems Availability #Before we get into a few examples that will back this up, let\u0026rsquo;s first talk about the components and their calculations that will need to come into play in this math problem.\nCircuits / MPLS Networks #The first part is looking at the circuits and MPLS networks (or other network types) that come into play. These are the easier part actually. The circuits and network from providers will come with an availability SLA. Now there are other factors that come into play on whether or not the carrier/provider meet the SLAs. But that is the percentage number that will be used in calculating.\nDevice Availability #The calculation provided by EventHelix\u0026rsquo;s post makes sense here. The availability of a network device is calculated from having two numbers. MTBF (Mean Time Between Failure) and MTTR (Mean Time To Recovery). MTBF is a figure that is provided about how many hours that a device is expected to operate between failures. This is a number that is often available from your hardware vendors, and increasingly more difficult to find publicly. The MTTR is a number that you are expecting for how long it may take to replace the device or recover from a failure manually. In some environments this may be an SLA from a provider as well or you may have to put an educated guess to this.\nSystem Availability #From the calculation documented before, there are two parts that I do when calculating a WAN availability. First I flatten down to get the components into a series as much as possible. So if you have two routers providing WAN edge services to an MPLS cloud, the first thing you calculate is the flattened availability of the WAN edge. Once you have everything in a series, the math becomes pretty quick.\nAvailability = (MTBF) / (MTBF + MTTR)\nParallel System Availability #At each of the layers, you break things down into just a single availability for each of the systems. This means that you need to be able to calculate for a system that has parallel availability. You will see more in the second example below. So for now, the calculation that you need to know is that for finding availability of a system component that has a parallel option is:\nAvailability = 1 - (1 - Ax) * (1 - Ay)\nWhere Ax is the availability of component x (first circuit) and Ay is the availability of component y (secondary circuit).\nWAN Design Availability Examples #Let\u0026rsquo;s start with some designs as a way to calculate the expected availability of a WAN environment. For the device types, I am going to use various Meraki devices as a method to help show since there is a post that is publicly available of what the MTBF is for the devices. Take a look here for the numbers that are posted (into a table below).\nOne assumption that does also need to be taken into account is power as well. That will be the one component that needs to get calculated in that for the sake of the blog we will assume will be 100% availability. Device MTBF MR42 450,000h MX64 1,273,000h MX84 925,000h MX100 389,000h MX250 336,800h MX450 336,800h Basic, Two Routers Connected Via Point to Point Circuit #This is the design basic components. There is only one component at each level:\nOk, so there are a few options now, other than the circuits having a single availability percentage. Let\u0026rsquo;s take a look at a table of what these may look like for site availability. The WAN circuit will have an SLA of 99.5% availability and let\u0026rsquo;s explore using different pieces of hardware.\nWAN Edge Device Site Edge Device Edge Repair Time Site Repair Time WAN Edge Availability Site Edge Availability WAN Availability Total Availability MX84 MR42 8h 24h 99.99914% 99.99822% 99.5% 99.497% (2642 minutes of downtime) MX84 MX64 8h 24h 99.99914% 99.99937% 99.5% 99.499% (2636 minutes of downtime) MX64 MX64 8h 24h 99.99937% 99.99937% 99.5% 99.499% (2635 minutes of downtime) What we see here is that while hardware absolutely comes into play for helping on the availability. The MX64 which has a much longer MTBF, 4 times as much, will still only get an expected availability of 7 minutes more of up time. The limiter is still the single WAN circuit availability of 99.5%.\nSmall WAN, Adding a Backup Link # Now it has been a while since I have been ordering circuits, so I\u0026rsquo;m going to go with an extreme light example as a backup circuit. I don\u0026rsquo;t know that cellular providers are providing a SLA of service availability, but let\u0026rsquo;s add a secondary circuit that would be cellular or broadband provider. Let\u0026rsquo;s then assume that the second circuit has an availability of 99% along with it, which would allow for 3 days+ per year of service outage.\nUsing the calculation above for calculating the availability of a secondary circuit you now get a WAN availability of 99.99% - four nines by just adding an inexpensive WAN circuit. Let\u0026rsquo;s see what that does to the calculations of availability:\nWAN Edge Device Site Edge Device Edge Repair Time Site Repair Time WAN Edge Availability Site Edge Availability WAN Availability Total Availability MX84 MR42 8h 24h 99.99914% 99.99822% 99.995% 99.992% (40 minutes of downtime) MX84 MX64 8h 24h 99.99914% 99.99937% 99.995% 99.99% (34 minutes of downtime) MX64 MX64 8h 24h 99.99937% 99.99937% 99.995% 99.998% (33 minutes of downtime) Very quickly are we getting to diminishing returns on what we can add to the system to make it more available before it gets too complex. Let\u0026rsquo;s throw a second backup link into the mix before we start to explore adding in additional components at the WAN Edge layer.\nSmall WAN, Two Backup Links # This is where the design may start to get a bit unwieldy, and there is a cost for the complexity of hte network. That is just much more difficult to quantify. When adding a third circuit that has a 99% availability number with it, you get to having 6 nines and a 5. The 5 comes from the original circuit of 99.5% availability.\nWAN Edge Device Site Edge Device Edge Repair Time Site Repair Time WAN Edge Availability Site Edge Availability WAN Availability Total Availability MX84 MR42 8h 24h 99.99914% 99.99822% 99.99995% 99.997% (14 minutes of downtime) MX84 MX64 8h 24h 99.99914% 99.99937% 99.99995% 99.998% (8 minutes of downtime) MX64 MX64 8h 24h 99.99937% 99.99937% 99.99995% 99.999% (7 minutes of downtime) The third circuit gets your site availability expectation to 7 minutes of downtime where both ends of the circuit are having MX64 devices terminate the circuits. That is getting pretty darn good on the availability.\nMPLS Network With Two Uplinks At Edge, Site with One and Backup #One of the common practices now days is to have multiple edge routers at the WAN edge in the data center that provides connectivity to the cloud portion of the WAN run by the providers. Let\u0026rsquo;s take a look at this design:\nNow you can see that the layers have expanded here significantly. There are now five layers to take into consideration. Rather than include the repair times in the table, I\u0026rsquo;m removing those for brevity here.\nSite Edge Availability Site WAN Availability (Not the Backup Link) MPLS Availability WAN Edge Availability to Provider Backup Network Availability WAN Edge Availability Total Availability 99.99937% 99.5% 99.95% 99.9975% 99% 99.99874% 99.9925% (39 minutes) This shows that the availability number here is slightly worse than what you may get from having multiple back ups earlier above. This is where the network architecture also needs to take into account the criticality of the path. If this is the design for a single site to single site it may not be optimal (depending on the service profile). Summary #When it comes to WAN design, there are multiple options and methods to design your Wide Area Network. You are able to put together calculations to be able to affirm that you have the right design to meet the business requirements for the WAN. The next challenge is then to get the appropriate uptime metric as often you may get \u0026ldquo;The WAN needs to be online all of the time\u0026rdquo;. At this point you can put together general costs of providing the services at different service levels. This has been one of my favorite topics over the years and wanted to share. Hope that some may have found this helpful in articulating WAN design and costs!\n-Josh\n","date":"2023-09-02","permalink":"https://josh-v.com/designing-wan-availability/","section":"Posts","summary":"\u003cp\u003eOne of my hot topics in my past that I haven\u0026rsquo;t seen written about often is the calculation of WAN availability and what the design is built for. There is often the number of 9\u0026rsquo;s whether that is 5 9s or 3 9s or otherwise, where do you start? Well, in the past the \u003ca href=\"https://www.eventhelix.com/fault-handling/system-reliability-availability/\" target=\"_blank\" rel=\"noreferrer\"\u003epost by EventHelix.com\u003c/a\u003e outlines system reliability. It talks about designing systems in parallel and in serial. What does that mean. Well, I am going to take the system availability and bring it into the Wide Area Network, which really could be brought to any environment that has a system uptime requirement as a way to calculate and validate the dollars that you are requesting. I am hoping that this will help you to be able to answer questions such as \u0026ldquo;What if we added another service provider?\u0026rdquo; or \u0026ldquo;What if we changed out hardware for a smaller/larger hardware choice?\u0026rdquo;.\u003c/p\u003e","title":"Designing WAN Availability"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/wan/","section":"Tags","summary":"","title":"Wan"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/devnet/","section":"Tags","summary":"","title":"Devnet"},{"content":"As part of my journey of using my Debian based Dev Workstation, as well as my studies towards completion of the DevNet Expert, I wanted to get up and running with the DevNet Workstation example that would help to become familiar with the environment that would be found at the live exam. There were a few small quirks along the way, so I thought I would go ahead and create a post about how to get started.\nGetting Started #The process will look like the following:\nDownload the image file Import the image into a new VM Validate the access, check for Internet access Modify the Netplan Restart and enjoy! Download the Image #The first part of the process is to get the qcow2 file downloaded from the Equipment and Software List in the Candidate Workstation section. Once the file is downloaded, I recommend moving the file into a safe location that is not the Downloads folder. That seems to be a place that tends to get overwhelmed with data and then periodically purged.\nOnce downloaded, then you can create a new VM, I will be using Virtual Machine Manager as a UI to the KVM system. I won\u0026rsquo;t dive into deep details of the under the hood of what is going on, but get into how I am able to get the host available and online.\nI recommend taking a look at Chris Titus\u0026rsquo;s Tech blog (and likely his YouTube video associated with it) for getting Qemu/KVM and Virtual Machine Manager going. Import the Image to a New VM #Now the fun part, which is pretty quick to get going. From the base VMM screen, select new on the upper left hand corner.\nOn the new window pop up, then select import existing disk image.\nBrowse to the location of the disk, then on the bottom section type out Ubuntu 20 to find the OS type of Ubuntu, select Forward.\nSelect the resources that you want to make available to the host. This is something that you may want to consider giving a few more to than the default, however Linux OS do seem to handle limited resources quite well.\nOn step 4 you get the option to name the virtual machine. Here it is good to go ahead and give it a meaningful name. Be sure to expand the Network selection section, and have this assigned to the bridge interface that is created during the set up of the system for Qemu.\nOnce you have completed this, now the machine will boot up. The login based on the docs (and thank you Jeff) is 1234QWer!.\nValidate Internet Access #To verify what I had seen previously, I test a ping 1.1.1.1 to see if there is Internet access and there is not.\nNetplan #Taking a look at the network connections I run the command ip add and I get the following output, where the interface name is highlighted on line 8.\n1 2 3 4 5 6 7 8 9 10 11 12 13 (main) expert@expert-cws:~$ ip add 1: lo: \u0026lt;LOOPBACK,UP,LOWER_UP\u0026gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: enp1s0: \u0026lt;BROADCAST,MULTICAST\u0026gt; mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 52:54:00:17:69:d0 brd ff:ff:ff:ff:ff:ff 3: docker0: \u0026lt;NO-CARRIER,BROADCAST,MULTICAST,UP\u0026gt; mtu 1500 qdisc noqueue state DOWN group default link/ether 02:42:74:e1:0a:fc brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0 valid_lft forever preferred_lft forever On this same host, showing the netplan with the command cat /etc/netplan/00-cws-dhcp-config.yaml (tab complete after /etc/netplan gets you the file name) you can see that on line 6 that the default workstation interface name has ens3 rather than the enp1s0 that you saw on the previous command:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # Configure ens160 for DHCP network: version: 2 renderer: networkd ethernets: ens3: dhcp4: true # Reference: Configuring Static IP address # network: # version: 2 # renderer: networkd # ethernets: # ens160: # addresses: # - 10.10.10.2/24 # nameservers: # search: [mydomain, otherdomain] # addresses: [10.10.10.1, 1.1.1.1] # routes: # - to: default # via: 10.10.10.1 Updating NetPlan #Now, this is where a little bit of Linux commands come out. The user expert is not in the sudoers list, so you are not able to use sudo to modify the file. However, I found that bringing up the root shell of the root user with su - does in fact get you into the root user prompt.\n(main) expert@expert-cws:~$ su - Password: root@expert-cws:~# With the prompt you see that you are now the root user by the username at the front and the # prompt.\nNow you can modify the Netplan using vi or nano, or whatever your favorite text editor is from the CLI. While modifying the file, swap out the ens3: for whatever interface name that you found on the ip add command earlier. In this case making the interface name enp1s0. So the output looks like:\nroot@expert-cws:~# cat /etc/netplan/00-cws-dhcp-config.yaml # Source: https://netplan.io/examples/ # Configure ens160 for DHCP network: version: 2 renderer: networkd ethernets: enp1s0: dhcp4: true Once the file is saved, now execute netplan apply. You should have no output and bring you back to the same prompt. Now you can ping an address on the internet and get success:\nroot@expert-cws:~# ping 1.1.1.1 -c 4 PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data. 64 bytes from 1.1.1.1: icmp_seq=1 ttl=55 time=16.2 ms 64 bytes from 1.1.1.1: icmp_seq=2 ttl=55 time=15.8 ms 64 bytes from 1.1.1.1: icmp_seq=3 ttl=55 time=14.8 ms 64 bytes from 1.1.1.1: icmp_seq=4 ttl=55 time=15.4 ms --- 1.1.1.1 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3005ms rtt min/avg/max/mdev = 14.796/15.568/16.226/0.525 ms Summary #At this point you now have another device on the network for you to use. You can set up VNC or other system to remote into the environment and use, or just use the Virtual Machine Manager display to work within the workstation. This is something that took me a small bit of time and I was glad to be able to figure out how to accomplish getting the system up and running. What will you do with the DevNet Workstation? Let me know in comments below or on social media!\nJosh\n","date":"2023-08-23","permalink":"https://josh-v.com/devnet-expert-workstation-on-debian/","section":"Posts","summary":"\u003cp\u003eAs part of my journey of using my Debian based Dev Workstation, as well as my studies towards completion of the DevNet Expert, I wanted to get up and running with the \u003ca href=\"https://learningnetwork.cisco.com/s/article/devnet-expert-equipment-and-software-list\" target=\"_blank\" rel=\"noreferrer\"\u003eDevNet Workstation\u003c/a\u003e example that would help to become familiar with the environment that would be found at the live exam. There were a few small quirks along the way, so I thought I would go ahead and create a post about how to get started.\u003c/p\u003e","title":"DevNet Expert Workstation On Debian"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/expert/","section":"Tags","summary":"","title":"Expert"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/authentication/","section":"Tags","summary":"","title":"Authentication"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/debian/","section":"Tags","summary":"","title":"Debian"},{"content":"As a long time MacBook user and of recent years on the M1 using the TouchID system that allows for fingerprint authentication, this is something that I wanted to get to work pretty quickly for myself. I had tried a couple of different options to get fingerprint reading to work. Through the 3 methods, I finally have one that works, and I figured it would be worth the share.\nAttempted Routes #Now some of these may seem foolish as I write after the fact. But I had some reason to think that there would be success. The three options that I tried (only the last/third one worked):\nKensington VeriMark Yubikey Bio DigitalPersona 4500 Kensington Verimark #Why did I try this? Well, when I searched in the Amazon search box, I searched Linux Desktop Fingerprint Reader. This was Amazon\u0026rsquo;s top choice. I went with it before researching further - my mistake. Once I received the tiny USB device, plugged it in, and nothing happened I went looking a bit further. Upon further research, this only had drivers for Windows and Mac. Makes sense. Those are the two biggest players. So this was a no go.\nYubikey Bio #I thought to myself, let\u0026rsquo;s try the Yubikey Bio, since there is a fingerprint component to it, maybe it would work for fingerprint auth. I could use a Yubikey anyhow myself. This didn\u0026rsquo;t work, kind of obvious with having a third attempt upcoming.\nDigitalPersona 4500 #Next I finally found the Debian docs on supporting fingerprint authentication. In the page there is a list of supported devices which gave me a few options to run down. I came across the Digital Persona device, which looks a like many of the finger print readers at various medical providers around. I felt better and gave this a go. I went with the Digital Persona 4500 off of Amazon. Instant success. I was able to get the finger print reader up and working. I had followed some other online docs that indicated that you only needed to install fprintd, this is wrong. You do need fprintd and libpam-fprintd, which is clearly listed on the Debian docs.\nReading in the FingerPrint #To read in the finger print on Debian, go to your Activities and search for Users. This will bring up the Users setting panel. Now you should have an option of Fingerprint Login, which may be set to off by default. Enable this and then you can go to adding some finger prints. You can see that I have a few finger prints already added.\nNow to add a new finger print, you select Scan new fingerprint. What is not as obvious in this next screen is the process to add your finger print. Do you just put the finger on one time and let it sit? Do I need to use the process that Apple (and likely Android devices) have around putting your finger on multiple times? I found that if I just leave my finger on the scanner, it doesn\u0026rsquo;t do much. So I do recommend the process of adding and removing your finger from the finger print reader. You will see the finger print icon change from grey to blue through the process.\nOnce it is complete, instead of blue or grey finger print icon you now get a green icon with complete indicated.\nSummary #Finger print authentication is coming along for those with a Debian desktop, not a laptop that has the finger print authentication built in. I\u0026rsquo;d ideally get this to work next with 1Password and other systems to not have to enter credentials. I believe this will be coming soon. Until then I do like the final choice of the Digital Persona 4500 to handle finger print authentication on my Linux desktop.\n","date":"2023-08-19","permalink":"https://josh-v.com/debian-finger-print-login/","section":"Posts","summary":"\u003cp\u003eAs a long time MacBook user and of recent years on the M1 using the TouchID system that allows for fingerprint authentication, this is something that I wanted to get to work pretty quickly for myself. I had tried a couple of different options to get fingerprint reading to work. Through the 3 methods, I finally have one that works, and I figured it would be worth the share.\u003c/p\u003e\n\u003ch2 id=\"attempted-routes\" class=\"relative group\"\u003eAttempted Routes \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#attempted-routes\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eNow some of these may seem foolish as I write after the fact. But I had some reason to think that there would be success. The three options that I tried (only the last/third one worked):\u003c/p\u003e","title":"Debian Finger Print Login"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/linux/","section":"Tags","summary":"","title":"Linux"},{"content":"Within Nautobot there are many ways to be able to get the Nautobot environment running. Environment variables are used quite a bit in the Docker environment following best practice principles set forth in the 12 Factor App. The use of environment variables is helpful for working through the various stages of an application to production. The installation instructions leverage a single environment variable NAUTOBOT_ROOT and that is set in the SystemD files shown below:\nSystemD File 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #/etc/systemd/system/nautobot.service [Unit] Description=Nautobot WSGI Service Documentation=https://docs.nautobot.com/projects/core/en/stable/ After=network-online.target Wants=network-online.target [Service] Type=simple Environment=\u0026#34;NAUTOBOT_ROOT=/opt/nautobot\u0026#34; User=nautobot Group=nautobot PIDFile=/var/tmp/nautobot.pid WorkingDirectory=/opt/nautobot ExecStart=/opt/nautobot/bin/nautobot-server start --pidfile /var/tmp/nautobot.pid --ini /opt/nautobot/uwsgi.ini ExecStop=/opt/nautobot/bin/nautobot-server start --stop /var/tmp/nautobot.pid ExecReload=/opt/nautobot/bin/nautobot-server start --reload /var/tmp/nautobot.pid Restart=on-failure RestartSec=30 PrivateTmp=true [Install] WantedBy=multi-user.target This is great if there are only a few environment variables, and since SystemD files are only available via the root user, there is some protection.\nEnvironment File #There is also a method that is supported of using an environment file. The environment file allows for putting several variables into a single file that can then be loaded by the application. Let\u0026rsquo;s start with the file format itself.\nThe file itself is recommended to live at the Nautobot root and be named .env, /opt/nautobot/.env. It really could be named anything, as long as it is known to you and your organization.\nEnvironment File Format #The format is an environment variable per line matching the syntax. So if you want to add to the environment NAPALM credentials for example, then you would have the following in the file, where the # is a comment when loading. You should be creating this file as the Nautobot user:\n# Change to the Nautobot User sudo -iu nautobot # Create the file, you can use Nano or other text editors if you choose. vim .env # /opt/nautobot/.env # NAPALM Credentials NAPALM_USERNAME=my_user NAPALM_PASSWORD=what_is_that_password_again Note on the environment variables show there are no quotes. You could also put quotes into the environment variable. Use the comment character to help to organize your credentials. So now there is a file with environment items, which may include credentials, now what?\nFile Permissions #It is a good practice to restrict the permissions on the file to that of the Nautobot user. So that only those that can get to the Nautobot user on the system are able to read the file. To update this to being only readable (and editable) to the Nautobot user. So this is executed as the Nautobot user again.\nchmod 0600 .env Using the Environment File #The last step in using the .env file that was created is to now reference that in the SystemD files. Note that you will need to make this change for each of the SystemD files including if using the core docs of nautobot, nautobot-worker, nautobot-scheduler. If there are any other files that you have added as well, you will need to update these. These files should be updated as the root user:\nsudo vi /etc/systemd/system/nautobot.service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # /etc/systemd/system/nautobot.service [Unit] Description=Nautobot WSGI Service Documentation=https://docs.nautobot.com/projects/core/en/stable/ After=network-online.target Wants=network-online.target [Service] Type=simple Environment=\u0026#34;NAUTOBOT_ROOT=/opt/nautobot\u0026#34; EnvironmentFile=/opt/nautobot/.env User=nautobot Group=nautobot PIDFile=/var/tmp/nautobot.pid WorkingDirectory=/opt/nautobot ExecStart=/opt/nautobot/bin/nautobot-server start --pidfile /var/tmp/nautobot.pid --ini /opt/nautobot/uwsgi.ini ExecStop=/opt/nautobot/bin/nautobot-server start --stop /var/tmp/nautobot.pid ExecReload=/opt/nautobot/bin/nautobot-server start --reload /var/tmp/nautobot.pid Restart=on-failure RestartSec=30 PrivateTmp=true [Install] WantedBy=multi-user.target It is important to note that there are no quotes/double quotes around the file path. If you put these in, there will be issues. I had invested a fair bit of time the first time troubleshooting why my environment variables are not loading. Once all of the files have been updated, you should complete a daemon reload:\nsudo systemctl daemon-reload Loading the Environment Variables on Login of Nautobot #Now there are variables in the file that by default do not get loaded. If you try to run source /opt/nautobot/.env then you will not have the proper format to load these. As the Nautobot user, modify the /opt/nautobot/.bashrc file, adding the following to the end of the file (from this gist). The highlighted line the .env file should match what you name the file.\n1 2 3 set -o allexport source /opt/nautobot/.env set +o allexport Now whenever you enter the bash prompt for the Nautobot user, then the environment is loaded. This is especially helpful if the database credentials are being controlled via the environment.\nSummary #I hope this helps out some folks over time on getting rolling with Nautobot and some of the capabilities. I myself have had to research this several times and wanted to get something out to be able to be a reference in order to get additional capabilities going. Let me know in the comments or on a social media link if you found this helpful!\nJosh\n","date":"2023-08-17","permalink":"https://josh-v.com/nautobot-environment-file/","section":"Posts","summary":"\u003cp\u003eWithin Nautobot there are many ways to be able to get the Nautobot environment running. Environment variables are used quite a bit in the Docker environment following best practice principles set forth in the \u003ca href=\"https://12factor.net/\" target=\"_blank\" rel=\"noreferrer\"\u003e12 Factor App\u003c/a\u003e. The use of environment variables is helpful for working through the various stages of an application to production. The installation instructions leverage a single environment variable \u003ccode\u003eNAUTOBOT_ROOT\u003c/code\u003e and that is set in the SystemD files shown below:\u003c/p\u003e","title":"Nautobot Environment File"},{"content":"One observation lately is that Python is moving along quickly with new versions and new EOLs. Along with needing to make these updates, the applications that Python uses will also need to be moving along. Nautobot is my favorite, and in my opinion the best SOT platform available in the open source ecosystem today. So let\u0026rsquo;s dive into the updating of the Python version.\nFor this post, I\u0026rsquo;ve created a new Rocky 8 Virtual Machine to be the host. See the note below for the reasoning. This will start off with a Nautobot install from the Nautobot docs. I won\u0026rsquo;t dive into all of that, assume that is the starting point with a fresh Nautobot application.\nI originally thought just doing a DNF update on the host that I would be able to use Python 3.8 or something of that nature. I was wrong. Running the DNF update put Python3.11 on the host. So now it is on to removing Python3.11 and move to 3.8 for this post. I completed this step with a sudo dnf install python3.8\nIt is a strong recommendation to not have to follow this process. In that you should be looking to be using infrastructure that can be destroyed and recreated. Such as with Docker containers or having the build process to be able to replace virtual machines as needed. My Original Challenges With Upgrading Python #Going back a few years I had the experience that was terribly painful to update Python versions on RHEL 7 and its derivatives. All of the documentation that I had found required to install Python from source, that you were not able to use DNF/YUM to just get the latest version of Python. Thankfully since RHEL8 and its derivatives, it is much simpler. Now DNF just is able to install and off we go.\nExplaining the Upgrade Path #If you followed the instructions on the Nautobot installation docs for your virtual machine build, this will become pretty quick. The installation method has a virtual environment created. So in order to update the version of Python, with the help of the Python VENV capabilities, it is a short process.\nInstall the desired version of Python on your system (sudo dnf install python3.11 or sudo apt install python3.11) Verify the Python command to run the latest version (python3 -V) Make a backup of the requirements and the directory Remove the existing virtual environment files Recreate the virtual environment with the new version of Python Re-install the Python packages Restart the Nautobot services The Upgrade #The process will begin with saving the current pip freeze requirements to a tmp file. THis is going to provide a quick method to re-install the Python packages required for your Nautobot instance.\npip freeze \u0026gt; /tmp/nautobot_requirements.txt Remove Bad Requirement #The backports.zoneinfo requirement that gets generated is deprecated and within Python3.9 should not be used. To accommodate for this remove any reference to backports.zoneinfo from the tmp/nautobot_requirements.txt file.\nCreate a Backup of Directory #With the file in place, now let\u0026rsquo;s move the copy the nautobot user directory to another directory. Most likely this will not be required, but if you need to do a restore of the directory having a backup is good practice.\n[user@rocky-nautobot ~]$ sudo cp -a /opt/nautobot /opt/nautobot-old Remove Existing Virtual Environment #Now to remove the existing Nautobot virtual environment by removing the bin directory (/opt/nautobot/bin).\nsudo -u nautobot rm -rf /opt/nautobot/bin Create New Virtual Environment #Create a new virtual environment into /opt/nautobot by using the same command from the install instructions.\nsudo -u nautobot python3 -m venv /opt/nautobot Now log in as the Nautobot user to update the pip and wheel packages. And from there install the rest of the requirements.txt items that were saved off earlier.\nsudo -iu nautobot pip install --upgrade pip wheel pip install -r /tmp/nautobot_requirements.txt Restart Services #The last step is to restart the services for Nautobot, which will then pick up the new Python version.\n# Exit out of the Nautobot user exit sudo systemctl restart nautobot nautobot-worker nautobot-scheduler Summary #While the recommended method for upgrading the Nautobot application is to create a new virtual machine, migrate the database over, and reclaim the previous host, that may not always be a viable option. This process is just tested out on my lab environment and has not been done in a production environment yet, but this should work.\n","date":"2023-08-14","permalink":"https://josh-v.com/upgrade-nautobot-python-virtual-machine/","section":"Posts","summary":"\u003cp\u003eOne observation lately is that Python is moving along quickly with new versions and new EOLs. Along with needing to make these updates, the applications that Python uses will also need to be moving along. Nautobot is my favorite, and in my opinion the best SOT platform available in the open source ecosystem today. So let\u0026rsquo;s dive into the updating of the Python version.\u003c/p\u003e\n\u003cp\u003eFor this post, I\u0026rsquo;ve created a new Rocky 8 Virtual Machine to be the host. See the note below for the reasoning. This will start off with a Nautobot \u003ca href=\"https://docs.nautobot.com/projects/core/en/stable/installation/\" target=\"_blank\" rel=\"noreferrer\"\u003einstall\u003c/a\u003e from the Nautobot docs. I won\u0026rsquo;t dive into all of that, assume that is the starting point with a fresh Nautobot application.\u003c/p\u003e","title":"Upgrade Nautobot Python Version in Virtual Machine"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/earth/","section":"Tags","summary":"","title":"Earth"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/golf/","section":"Tags","summary":"","title":"Golf"},{"content":"I\u0026rsquo;m going to diverge a small bit from the straight network automation space that I have blogged about primarily and dive a small bit into the world of using Google Earth to help prepare for your golf game. Upcoming, I\u0026rsquo;m playing in a Minnesota Golf event at two courses in late August. I\u0026rsquo;m going to put together a green book for myself and figure this would be a great topic to touch on how I\u0026rsquo;m going about this activity.\nRequirements #The first requirement is to have Google Earth installed on a desktop environment. This may be something that you can use the web version for, but I have had better luck using Google Earth Pro installed onto the desktop.\nGoogle Earth Pro GIMP The second requirement is a document (Microsoft Word, Google Doc, LibreOffice Writer) to build the content on. For this work I\u0026rsquo;m at least starting out with using Ubuntu 23 as my operating system. I will attempt to provide the same screen shot capabilities and editing for the Mac and Windows platforms as well.\nGetting Started with Google Earth #There are two main parts that I use when building my green books using Google Earth. The first is the ruler. This is how I am able to measure distances between points on the hole. The second piece is on the bottom right, there are some numbers to give you some more details, which I particularly focus on the elev which tells you what the elevation is when you hover over it..\nMapping the Course #The first thing is to search for the golf course within Google Earth. In the search box on the upper right I am typing in the first course, Chaska Town Course. This will take you from the globe view down to the golf course. Then you need to be able to find the first hole to start taking a screen shot for each hole. This may take some more investigation if you have never played the course before. Look for practice ranges and practice greens which are likely to be around the first tee. Also, if you are able to find a scorecard with the distances of each hole, you can get an idea using the ruler about which hole is which.\nOnce you have the hole, it is time to zoom in on just the hole so you can get a good screenshot. For making a green book, use the navigation on the upper right to change the orientation (although you may want to keep the orientation for understanding the wind). Once you have the orientation the way that you like it, take the screen shot.\nNow take that screen shot and place it into an image editor such as GIMP which is available for Windows, Mac, and Linux.\nMeasuring Distances to Obstacles / Tee Shot Expectations #The ruler on Google Earth is an amazing option to work with. This provides as you expect a measurement between two points. Now for the golf side of things, off of the tee you may want to make either one measurement from the middle of the expected tees, or a couple of measurements from the front and back of the tee boxes. This gives you the starting point of what to know once you are on the course. Measuring from the center of the tee box you will need to approximate where things are. I\u0026rsquo;m going to measure from the back of tee boxes, since often I will likely walk past the point and my pace of yards is known and I can get an understanding of how the course is playing from there.\nWith the ruler, you click once to set the first point, then click again to set the end point. The ruler itself then has a pop up window that provides the metrics, including the length of the line, and the heading at which the line is on. Measure the distance from your point to the various data points on the course. On the first hole at Chaska Town Course, I will map the distance to the bunker on the left hand side, it measures in the neighborhood of 274 yards from the back of the back most tee box to the sand trap, and 296 yards to the back of the sand trap. I may make two measurements, one from the most far back tee box, and one from the back of a middle tee box. This way there are two measurements to keep the pace of play moving in case one of the tees is at a location that is not expected.\nI\u0026rsquo;m not worrying about yardages from the objects to the green, except maybe on some par 5 holes. But once on the course and I\u0026rsquo;ve hit my tee shot, I will have a different distance to each area.\nMeasuring Elevation #Now that there are some yardages off the tee, comes the part that you don\u0026rsquo;t get from mobile golf apps today. You now want to plot the elevation. To do this, I hover my mouse over various points along the hole to give an idea of the elevation. And most importantly, I check out the 9 points of the green. I make a box around the green with the center elevation, elevation on the top left, top center, and so on. This is helpful to understand the general slope of the green and which way the ball is likely to break.\nThe USGA app itself also offers green books that have much more details slopes on them. There is an annual cost for this green book and I am one that is supporting technology innovations by the USGA. I\u0026rsquo;m glad to show interest and pay them a bit of revenue in order to continue to develop technology advances for golf. Android Apple Off of the tee is where you are likely to need this, as well as understanding various points along the way. So this way you know if you are hitting into an elevated green or not. Elevation changes do generally impact the distance that clubs go. So I\u0026rsquo;m taking the elevation at the tee box, the general fairway/rough areas, and since I\u0026rsquo;ve done this for the green book I then know if I\u0026rsquo;m going to be uphill or downhill to the hole.\nAll of these are manually entered into the GIMP editor. I\u0026rsquo;m using one color for yardages to the obstacles and another for the elevation numbers. I\u0026rsquo;m also going to put the two measurement points with an X to make sure to know where the two measurements are sourced from.\nFinal Look # Putting All Together #Once you have completed building your images of each hole, now you add the images to your document editor. This can be set up for however you may like size wise. You may need to work on how many pages you may want per page and the sizing that you would be looking for. I would be looking to put 6 holes on a page to be able to make the appropriate updates.\nSummary #This is not the most professional of green books out there. But then again, I\u0026rsquo;m not getting paid to play the game of golf either (at least not today). This is a way to look at some of the many great tools out there that are generally available for you to be able to leverage in your day to day. It may not be built specifically for this purpose, but another great use.\n","date":"2023-08-05","permalink":"https://josh-v.com/google-earth-golf/","section":"Posts","summary":"\u003cp\u003eI\u0026rsquo;m going to diverge a small bit from the straight network automation space that I have blogged about primarily and dive a small bit into the world of using Google Earth to help prepare for your golf game. Upcoming, I\u0026rsquo;m playing in a Minnesota Golf event at two courses in late August. I\u0026rsquo;m going to put together a green book for myself and figure this would be a great topic to touch on how I\u0026rsquo;m going about this activity.\u003c/p\u003e","title":"Using Google Earth for Golf"},{"content":"In my previous post I wrote about a workstation that I was working on building. It took an incredibly long time to get up and into a stable environment. But I have finally accomplished stability (hoping to not jinx it here with the post). I went through a fair bit of troubleshooting to get to this point.\nSymptom #The symptom that was having instability was that the system would freeze randomly. There was not a particular application or otherwise that would be point me to an application that was causing the failures. The system would just freeze overnight or at the start of getting into the desktop UI.\nI first tried multiple versions of Linux to see if there was a flavor of Linux that was causing the issue. To no avail. I tried:\nUbuntu 23 Fedora 38 PopOS 22.04 Debian 12 All of these had some sort of failure that was occurring within the system. Debian 12 wouldn\u0026rsquo;t even complete it\u0026rsquo;s full installation.\nOf these, PopOS did seem to work the longest. This was encouraging, but I was really interested in getting to Gnome 44, which has some excellent polish in the UI.\nHardware Testing #First up in the testing was to do some tests of the hardware. I went with doing a memory stress test using MemTest86+. With all 4 sticks of RAM in the system I received some errors on test #6 pretty quickly. So I was thinking that the next test would be to run tests on each individual stick of RAM. This test showed everything as clear. So I loaded all 4 sticks back into the system, and re-ran test #6 that gave errors pretty quickly. That was clear this time. Back to the OS! But then the freezing continued.\nNext up I had happened to pick up a second nVME SSD during Amazon\u0026rsquo;s Prime Days deals. This just happened to arrive after being placed on backorder instantly (that is another interesting quirk). So I go to install the nVME and get started installing. But the freezing kept happening.\nNext up was since I was going down the path of Fedora and the possible challenges (unfounded claim) that Nvidia drivers could be causing issues, I ordered a new GPU. Put this into the system and still no change.\nWhat I was really impressed on the swapping of the GPU was how quickly that Fedora was able to just pick this up and get moving. I just booted up and bam I had my UI going. Nothing to have to work through.\nAt this point, I am suspecting that maybe the motherboard would be my next stop on the troubleshooting chain. I decided to go to PC Parts Picker to look at what else would be compatible with the processor and RAM choices. I found the Gigabyte B765M DS3H AX would work, and it comes with some added benefits of having wireless (bluetooth) and a 2.5 Gbps NIC. I ordered up this motherboard and got to it the same night with completing the motherboard swap. Interestingly enough, it also includes a second M.2 interface that I was able to install both nVME drives that I have.\nInstantly things just felt better in the OS. Fedora was able to be installed smoothly and without any hesitation. The system BIOS already had XMP disabled, which was a recommendation that I had seen in some other posts. And the hardware was all detected.\nFedora ran through the night at this point. Once waking up to get started with the day it felt good to just have the Dev Workstation up and running.\nFedora Choice #I\u0026rsquo;m going with Fedora at the moment for a couple of reasons. First, on top of being a solid, trusted OS, it is running the latest kernels and latest UI. I\u0026rsquo;m impressed with where Linux is at. Secondly, I just need to get better. In the world of Network Automation, as much as it is easy to just get moving with Ubuntu, I need to be able to work within the world of Fedora. So this will hopefully work out well.\nDual Boot #One of the interesting things that I didn\u0026rsquo;t think about with the second drive, is that I have an easy method to set up a dual booting system. Every time my workstation reboots at this point I am presented with a choice of a previous PopOS install or the Fedora install. I am continuing to run Fedora, but it is nice to know that I have the option.\nSummary #So far it was an experience that I wasn\u0026rsquo;t looking to have, but I did get to experience desktop hardware troubleshooting. It\u0026rsquo;s interesting space that I hadn\u0026rsquo;t had the opportunity to do for a while. Just the timing of this could have been better. Well, time to return that old motherboard.\nJosh\n","date":"2023-07-29","permalink":"https://josh-v.com/workstation-troubleshooting-2023/","section":"Posts","summary":"\u003cp\u003eIn my \u003ca href=\"https://josh-v.com/desktop-build-2023/\" target=\"_blank\" rel=\"noreferrer\"\u003eprevious post\u003c/a\u003e I wrote about a workstation that I was working on building. It took an incredibly long time to get up and into a stable environment. But I have finally accomplished stability (hoping to not jinx it here with the post). I went through a fair bit of troubleshooting to get to this point.\u003c/p\u003e\n\u003ch2 id=\"symptom\" class=\"relative group\"\u003eSymptom \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#symptom\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eThe symptom that was having instability was that the system would freeze randomly. There was not a particular application or otherwise that would be point me to an application that was causing the failures. The system would just freeze overnight or at the start of getting into the desktop UI.\u003c/p\u003e","title":"Workstation Troubleshooting 2023"},{"content":"Here I\u0026rsquo;m going to dive into what I\u0026rsquo;m planning to build out for my next desktop here in 2023. Prime Day is nearly upon us, and I\u0026rsquo;m anticipating (but do not know for sure) that prices on some of the gear that I\u0026rsquo;m looking for will be available at a good price. I\u0026rsquo;m also looking to build out a bigger system in order to run some intense VMs up coming.\nMy goals:\nBuild a system that will last for 3-4 years at a minimum Max out the RAM, that is my most limiting factor in my environments Give Linux a try as the desktop OS, still a bit of debate in this, considering options: Debian 12 POP OS Linux Mint The System #What I\u0026rsquo;m going with are the following:\nComponent Item Amazon Link Processor Intel i5-12600K https://amzn.to/3XLsi3H Motherboard ASUS Prime B760M-A Gigabyte B765M DS3H AX https://amzn.to/3OhpdEe Memory Qty 2, 2 x 32 GB TEAMGROUP Elite DDR5 https://amzn.to/3rp0RAR Storage Samsung 980 Pro SSD NVMe https://amzn.to/3PS3Fk3 Case Cooler Master MasterBox Q300L https://amzn.to/43kHF4j Power Supply Thermaltake SMART 600W https://amzn.to/3NPuCCt CPU Cooler DeepCool AK400 https://amzn.to/3rrpM6y GPU AISURIX Radeon RX 580 https://amzn.to/3Yf4B4a These are all affiliate links. I am not a big affiliate person today, but trying it out. I am making some minor updates based on changes that I had made in troubleshooting. I have swapped out what originally was an ASUS Prime B760M motherboard for a Gigabyte B765M DS3H AX. This provides additional networking capabilities (Wifi/Bluetooth and 2.5 Gbps NIC) and an additional M.2 slot. I have also swapped out the GPU and will stick with the new GPU at this point. Processor #I was in between the i5 and i7-12700K processor for this. As I look at my systems though, I don\u0026rsquo;t really ever touch the CPU and looking at Passmark on power usage, the i7 (as expected) has a higher TDP, at which point for the amount of idle time that I do expect on the system I stuck with the i5-12600K. It has plenty of speed, and should support what I\u0026rsquo;m looking to do no issue.\nMotherboard #I was originally looking at the most inexpensive 128 GB DDR5 capable motherboard that I could find. In the end I went with a little bit better one to get a 2.5 Gbps NIC, not that I will need it. But its available for the future.\nMemory #This was what I needed. All of my systems that I have are feeling the pinch when it comes to allocation of RAM. The CPUs are not being touched, but the memory is definitely running pretty hot. So this is where I am maxing out the memory with some DDR5-4800, which is what the CPU recommends. I\u0026rsquo;m not looking to overclock anything, but this works.\nStorage #Nothing too fancy here. Going with a good amount of memory. I utilize SAN connectivity often and will be the case here as well that I will mount a few folders on my SAN to account for anything that starts to seem excessive.\nCase #This is where I go small and don\u0026rsquo;t need much. I just need the case to be there to house the gear and protect it. The Cooler Master MasterBox is small enough and should be just what I need.\nPower Supply #Similar story as the others, that the system didn\u0026rsquo;t need to be too heavy. So going with something light on the CPU should be just fine for me. I could see a reverse course on this decision at some point.\nCPU Cooler #I have seen good reviews on this and my goal is for quiet. Being a bit more of a budget PC yet, that is what I have decided to go with on an inexpensive front.\nGraphics #Since I\u0026rsquo;m not doing a ton of gaming, I am likely to either just use the integrated graphics or leverage an existing GPU that I have in the house. With this becoming my primary desktop I will move an NVIDIA card that I already have into the new unit, and take an older card to replace it in my previous desktop that will eventually become another Proxmox node.\nWhat Will I Do With The System? #So what will I be putting onto this system?\nCisco CML With the expanded memory I should be able to run at least one IOS-XR device Will run plenty of Arista / Nexus switches to better simulate a DC Dev Host Possibly some GitHub Actions runners Few other VMs that are OK to be rebooted periodically For those that I want to have more generally available I have a few NUCs that are running that do the same thing Attempting to integrate Libvirt and Proxmox together Happy Prime Day #With Prime Day going on, there are plenty of deals to be had with these items. I in fact had purchased a few of these before thinking that they were already a good price. But then Prime Day deals came in even better. Look for more posts to come in the future!\nJosh\n","date":"2023-07-09","permalink":"https://josh-v.com/desktop-build-2023/","section":"Posts","summary":"\u003cp\u003eHere I\u0026rsquo;m going to dive into what I\u0026rsquo;m planning to build out for my next desktop here in 2023. Prime Day is nearly upon us, and I\u0026rsquo;m anticipating (but do not know for sure) that prices on some of the gear that I\u0026rsquo;m looking for will be available at a good price. I\u0026rsquo;m also looking to build out a bigger system in order to run some intense VMs up coming.\u003c/p\u003e","title":"Desktop Build 2023"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/golang/","section":"Tags","summary":"","title":"Golang"},{"content":"One of Nautobot\u0026rsquo;s primary functions is to serve as an IPAM solution. Within that realm, the application needs to provide a method to get at IP address data for a device, quickly and easily. In this post I will review three prominent methods to get an IP address from Nautobot. It will demonstrate getting the address via:\nNautobot REST API curl Python Requests GoLang HTTP pynautobot Ansible Lookup Nautobot GraphQL API curl Python Requests GoLang HTTP pynautobot Ansible Lookup Each method I will demonstrate how to get the IP address for Loopback0 on the device bre01-edge-01 within the demo instance of Nautobot. This device has 62 interfaces, so being able to filter down to which interface IP address we are looking for makes sense.\nGetting an IP Address From The API #For this set up, the environment variables of NAUTOBOT_URL and NAUTOBOT_TOKEN will be used. Set those with\nexport NAUTOBOT_URL=https://demo.nautobot.com export NAUTOBOT_TOKEN=secretTokenHere curl #The first straight forward method is going to be using the curl application to accomplish the goal.\n1 2 3 4 curl -X \u0026#34;GET\u0026#34; \\ \u0026#34;$NAUTOBOT_URL/api/ipam/ip-addresses/?device=bre01-edge-01\u0026amp;interface=Loopback0\u0026#34; \\ -H \u0026#34;accept: application/json\u0026#34; \\ -H \u0026#34;Authorization: Token $NAUTOBOT_TOKEN\u0026#34; This requires the quotes to be double quotes. Bash and shell prompts will not expand it if it is single quotes.\nThis is the full API response that is provided then:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 { \u0026#34;count\u0026#34;: 1, \u0026#34;next\u0026#34;: null, \u0026#34;previous\u0026#34;: null, \u0026#34;results\u0026#34;: [ { \u0026#34;id\u0026#34;: \u0026#34;77371932-3b7f-4e94-9179-d1d4290695d9\u0026#34;, \u0026#34;display\u0026#34;: \u0026#34;10.30.128.1/32\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://demo.nautobot.com/api/ipam/ip-addresses/77371932-3b7f-4e94-9179-d1d4290695d9/\u0026#34;, \u0026#34;family\u0026#34;: { \u0026#34;value\u0026#34;: 4, \u0026#34;label\u0026#34;: \u0026#34;IPv4\u0026#34; }, \u0026#34;address\u0026#34;: \u0026#34;10.30.128.1/32\u0026#34;, \u0026#34;vrf\u0026#34;: null, \u0026#34;tenant\u0026#34;: { \u0026#34;display\u0026#34;: \u0026#34;Nautobot Baseball Stadiums\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;a39f2dd8-84c8-4816-9e6f-4a7c46e91a77\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://demo.nautobot.com/api/tenancy/tenants/a39f2dd8-84c8-4816-9e6f-4a7c46e91a77/\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Nautobot Baseball Stadiums\u0026#34;, \u0026#34;slug\u0026#34;: \u0026#34;nautobot-baseball-stadiums\u0026#34; }, \u0026#34;status\u0026#34;: { \u0026#34;value\u0026#34;: \u0026#34;active\u0026#34;, \u0026#34;label\u0026#34;: \u0026#34;Active\u0026#34; }, \u0026#34;role\u0026#34;: null, \u0026#34;assigned_object_type\u0026#34;: \u0026#34;dcim.interface\u0026#34;, \u0026#34;assigned_object_id\u0026#34;: \u0026#34;6ecee964-e4e0-4a0a-83b7-b7485633fc78\u0026#34;, \u0026#34;assigned_object\u0026#34;: { \u0026#34;display\u0026#34;: \u0026#34;Loopback0\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;6ecee964-e4e0-4a0a-83b7-b7485633fc78\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://demo.nautobot.com/api/dcim/interfaces/6ecee964-e4e0-4a0a-83b7-b7485633fc78/\u0026#34;, \u0026#34;device\u0026#34;: { \u0026#34;display\u0026#34;: \u0026#34;bre01-edge-01\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;5e7c0bdd-254b-44cb-bf7c-2f2560082f6d\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://demo.nautobot.com/api/dcim/devices/5e7c0bdd-254b-44cb-bf7c-2f2560082f6d/\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;bre01-edge-01\u0026#34; }, \u0026#34;name\u0026#34;: \u0026#34;Loopback0\u0026#34;, \u0026#34;cable\u0026#34;: null }, \u0026#34;nat_inside\u0026#34;: null, \u0026#34;nat_outside\u0026#34;: null, \u0026#34;dns_name\u0026#34;: \u0026#34;edge-01.bre01.mlb.nautobot.com\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;created\u0026#34;: \u0026#34;2022-11-09\u0026#34;, \u0026#34;last_updated\u0026#34;: \u0026#34;2022-11-09T15:11:51.606550Z\u0026#34;, \u0026#34;tags\u0026#34;: [], \u0026#34;notes_url\u0026#34;: \u0026#34;https://demo.nautobot.com/api/ipam/ip-addresses/77371932-3b7f-4e94-9179-d1d4290695d9/notes/\u0026#34;, \u0026#34;custom_fields\u0026#34;: {} } ] } Python Requests # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from requests import Session import json import os url = f\u0026#34;{os.getenv(\u0026#39;NAUTOBOT_URL\u0026#39;)}/api/ipam/ip-addresses/?device=bre01-edge-01\u0026amp;interface=Loopback0\u0026#34; session = Session() session.headers = { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;, \u0026#34;Authorization\u0026#34;: f\u0026#34;Token {os.getenv(\u0026#39;NAUTOBOT_TOKEN\u0026#39;)}\u0026#34;, } response = session.get(url) ip_address = response.json()[\u0026#34;results\u0026#34;][0][\u0026#34;address\u0026#34;] print(ip_address) In this example I\u0026rsquo;ve used the requests Session method to store the headers instead of passing it in with requests.get(). They both work, but it is good habit to utilize a session when applicable and especially when making multiple API calls. The output is:\n❯ python get_ip_address.py 10.30.128.1/32 GoLang HTTP # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;strings\u0026#34; ) type IPAddress struct { Address string `json:\u0026#34;address\u0026#34;` } type Response struct { Results []IPAddress `json:\u0026#34;results\u0026#34;` } func main() { nautobotBase := os.Getenv(\u0026#34;NAUTOBOT_URL\u0026#34;) nautobotToken := os.Getenv(\u0026#34;NAUTOBOT_TOKEN\u0026#34;) url := fmt.Sprintf(\u0026#34;%s/api/ipam/ip-addresses/?device=bre01-edge-01\u0026amp;interface=Loopback0\u0026#34;, nautobotBase) method := \u0026#34;GET\u0026#34; payload := strings.NewReader(``) client := \u0026amp;http.Client{} req, err := http.NewRequest(method, url, payload) if err != nil { fmt.Println(err) return } tokenString := fmt.Sprintf(\u0026#34;Token %s\u0026#34;, nautobotToken) req.Header.Add(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) req.Header.Add(\u0026#34;Authorization\u0026#34;, tokenString) res, err := client.Do(req) if err != nil { fmt.Println(err) return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Println(err) return } // Parse the JSON response var response Response err = json.Unmarshal(body, \u0026amp;response) if err != nil { fmt.Println(err) return } // Print the desired value if len(response.Results) \u0026gt; 0 { fmt.Println(\u0026#34;IP Address:\u0026#34;, response.Results[0].Address) } else { fmt.Println(\u0026#34;No IP addresses found.\u0026#34;) } } This provides the same output as seen in the Python version:\n❯ go run get_ip.go IP Address: 10.30.128.1/32 pynautobot #The Python SDK that works to turn the Nautobot API into a Python object you can get the IP address data with the code below. Pynautobot examples are falling under the REST API section at the moment as that is where it fits the best.\n1 2 3 4 5 6 import os import pynautobot nautobot = pynautobot.api(url=os.getenv(\u0026#34;NAUTOBOT_URL\u0026#34;), token=os.getenv(\u0026#34;NAUTOBOT_TOKEN\u0026#34;)) ip_address = nautobot.ipam.ip_addresses.get(interface=\u0026#34;Loopback0\u0026#34;, device=\u0026#34;bre01-edge-01\u0026#34;) print(ip_address) The execution:\n1 2 ❯ python get_ip_address_sdk.py 10.30.128.1/32 REST API - Ansible #With Ansible, there are two methods available to use. You can use the native URI module that will gather data from the API endpoint. This example is with the Nautobot Ansible collection lookup plugin, which uses pynautobot under the hood. This allows for a little easier methodology of gathering data.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 --- - name: \u0026#34;GET IP ADDRESS FROM NAUTOBOT\u0026#34; hosts: localhost connection: local gather_facts: no vars: nautobot_url: \u0026#34;{{ lookup(\u0026#39;ansible.builtin.env\u0026#39;, \u0026#39;NAUTOBOT_URL\u0026#39;) }}\u0026#34; nautobot_token: \u0026#34;{{ lookup(\u0026#39;ansible.builtin.env\u0026#39;, \u0026#39;NAUTOBOT_TOKEN\u0026#39;) }}\u0026#34; tasks: - name: \u0026#34;10: GET IP ADDRESS FROM NAUTOBOT\u0026#34; set_fact: ip_address: \u0026#34;{{ lookup(\u0026#39;networktocode.nautobot.lookup\u0026#39;, \u0026#39;ip-addresses\u0026#39;, api_endpoint=nautobot_url, token=nautobot_token, api_filter=\u0026#39;device=bre01-edge-01 interface=Loopback0\u0026#39;) }}\u0026#34; - debug: msg: \u0026#34;{{ ip_address[\u0026#39;value\u0026#39;][\u0026#39;address\u0026#39;] }}\u0026#34; Which gives the following output.\nPLAYBOOK: get_ip.yml *********************************************************************************************************************** 1 plays in get_ip.yml PLAY [GET IP ADDRESS FROM NAUTOBOT] ******************************************************************************************************** META: ran handlers TASK [10: GET IP ADDRESS FROM NAUTOBOT] **************************************************************************************************** task path: /home/joshv/projects/sandbox-ansible/get_ip.yml:10 ok: [localhost] =\u0026gt; { \u0026#34;ansible_facts\u0026#34;: { \u0026#34;ip_address\u0026#34;: { \u0026#34;key\u0026#34;: \u0026#34;77371932-3b7f-4e94-9179-d1d4290695d9\u0026#34;, \u0026#34;value\u0026#34;: { \u0026#34;address\u0026#34;: \u0026#34;10.30.128.1/32\u0026#34;, \u0026#34;assigned_object\u0026#34;: { \u0026#34;cable\u0026#34;: null, \u0026#34;device\u0026#34;: { \u0026#34;display\u0026#34;: \u0026#34;bre01-edge-01\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;5e7c0bdd-254b-44cb-bf7c-2f2560082f6d\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;bre01-edge-01\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://demo.nautobot.com/api/dcim/devices/5e7c0bdd-254b-44cb-bf7c-2f2560082f6d/\u0026#34; }, \u0026#34;display\u0026#34;: \u0026#34;Loopback0\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;6ecee964-e4e0-4a0a-83b7-b7485633fc78\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Loopback0\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://demo.nautobot.com/api/dcim/interfaces/6ecee964-e4e0-4a0a-83b7-b7485633fc78/\u0026#34; }, \u0026#34;assigned_object_id\u0026#34;: \u0026#34;6ecee964-e4e0-4a0a-83b7-b7485633fc78\u0026#34;, \u0026#34;assigned_object_type\u0026#34;: \u0026#34;dcim.interface\u0026#34;, \u0026#34;created\u0026#34;: \u0026#34;2022-11-09\u0026#34;, \u0026#34;custom_fields\u0026#34;: {}, \u0026#34;description\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;display\u0026#34;: \u0026#34;10.30.128.1/32\u0026#34;, \u0026#34;dns_name\u0026#34;: \u0026#34;edge-01.bre01.mlb.nautobot.com\u0026#34;, \u0026#34;family\u0026#34;: { \u0026#34;label\u0026#34;: \u0026#34;IPv4\u0026#34;, \u0026#34;value\u0026#34;: 4 }, \u0026#34;id\u0026#34;: \u0026#34;77371932-3b7f-4e94-9179-d1d4290695d9\u0026#34;, \u0026#34;last_updated\u0026#34;: \u0026#34;2022-11-09T15:11:51.606550Z\u0026#34;, \u0026#34;nat_inside\u0026#34;: null, \u0026#34;nat_outside\u0026#34;: null, \u0026#34;notes_url\u0026#34;: \u0026#34;https://demo.nautobot.com/api/ipam/ip-addresses/77371932-3b7f-4e94-9179-d1d4290695d9/notes/\u0026#34;, \u0026#34;role\u0026#34;: null, \u0026#34;status\u0026#34;: { \u0026#34;label\u0026#34;: \u0026#34;Active\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;active\u0026#34; }, \u0026#34;tags\u0026#34;: [], \u0026#34;tenant\u0026#34;: { \u0026#34;display\u0026#34;: \u0026#34;Nautobot Baseball Stadiums\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;a39f2dd8-84c8-4816-9e6f-4a7c46e91a77\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Nautobot Baseball Stadiums\u0026#34;, \u0026#34;slug\u0026#34;: \u0026#34;nautobot-baseball-stadiums\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://demo.nautobot.com/api/tenancy/tenants/a39f2dd8-84c8-4816-9e6f-4a7c46e91a77/\u0026#34; }, \u0026#34;url\u0026#34;: \u0026#34;https://demo.nautobot.com/api/ipam/ip-addresses/77371932-3b7f-4e94-9179-d1d4290695d9/\u0026#34;, \u0026#34;vrf\u0026#34;: null } } }, \u0026#34;changed\u0026#34;: false } TASK [debug] ******************************************************************************************************************************* task path: /home/joshv/projects/sandbox-ansible/get_ip.yml:18 ok: [localhost] =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;10.30.128.1/32\u0026#34; } META: ran handlers META: ran handlers PLAY RECAP ********************************************************************************************************************************* localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 GraphQL #The second methodology, which is the preferred method to get data from Nautobot, as it provides the data you are looking for only, it doesn\u0026rsquo;t get all of the additional data that comes with the REST API calls. The best methodology for discovering the GraphQL query is by using the iQL interface. This is by selecting GraphQL icon on the bottom right of the Nautobot instance.\nThe query that is going to be used here:\n1 2 3 4 5 query { ip_addresses(device:\u0026#34;bre01-edge-01\u0026#34;, interface: \u0026#34;Loopback0\u0026#34;) { address } } On line 2 the query is indicating to search ip_addresses from Nautobot. Then to filter on the device by name and the interface by interface name. You can turn these into variables as well within the GraphQL standards. The returned response is:\n{ \u0026#34;data\u0026#34;: { \u0026#34;ip_addresses\u0026#34;: [ { \u0026#34;address\u0026#34;: \u0026#34;10.30.128.1/32\u0026#34; } ] } } Let\u0026rsquo;s take a look at how this is then accomplished with the various methods.\nGraphQL - Curl #curl --location \u0026#34;$NAUTOBOT_URL/api/graphql/\u0026#34; \\ --header \u0026#34;Content-Type: application/json\u0026#34; \\ --header \u0026#34;Authorization: Token $NAUTOBOT_TOKEN\u0026#34; \\ --data \u0026#39;{\u0026#34;query\u0026#34;:\u0026#34;query {\\n ip_addresses(device:\\\u0026#34;bre01-edge-01\\\u0026#34;, interface: \\\u0026#34;Loopback0\\\u0026#34;) {\\n address\\n }\\n}\\n\u0026#34;,\u0026#34;variables\u0026#34;:{}}\u0026#39; The response is back as expected, just in the printed format.\nPython Requests # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from requests import Session import json import os url = \u0026#34;https://demo.nautobot.com/api/graphql/\u0026#34; session = Session() session.headers = { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;, \u0026#34;Authorization\u0026#34;: f\u0026#34;Token {os.getenv(\u0026#39;NAUTOBOT_TOKEN\u0026#39;)}\u0026#34;, } payload = { \u0026#34;query\u0026#34;: \u0026#34;\u0026#34;\u0026#34; query { ip_addresses(device:\u0026#34;bre01-edge-01\u0026#34;, interface: \u0026#34;Loopback0\u0026#34;) { address } } \u0026#34;\u0026#34;\u0026#34; } response = session.post(url, json=payload) ip_address = response.json()[\u0026#34;data\u0026#34;][\u0026#34;ip_addresses\u0026#34;][0][\u0026#34;address\u0026#34;] print(ip_address) The structure returned by GraphQL is a little bit different than the REST API, but nothing that we can\u0026rsquo;t work through as seen on line 23.\nGoLang # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;strings\u0026#34; ) type IPAddressResponse struct { Data struct { IPAddresses []struct { Address string `json:\u0026#34;address\u0026#34;` } `json:\u0026#34;ip_addresses\u0026#34;` } `json:\u0026#34;data\u0026#34;` } // Define a struct to represent the GraphQL query and its variables type GraphQLRequest struct { Query string `json:\u0026#34;query\u0026#34;` Variables struct{} `json:\u0026#34;variables\u0026#34;` } func main() { nautobotUrl := os.Getenv(\u0026#34;NAUTOBOT_URL\u0026#34;) nautobotToken := os.Getenv(\u0026#34;NAUTOBOT_TOKEN\u0026#34;) url := fmt.Sprintf(\u0026#34;%s/api/graphql/\u0026#34;, nautobotUrl) method := \u0026#34;POST\u0026#34; // Create a GraphQLRequest object with the query and an empty variables object graphQLRequest := GraphQLRequest{ Query: `query { ip_addresses(device:\u0026#34;bre01-edge-01\u0026#34;, interface: \u0026#34;Loopback0\u0026#34;) { address } }`, Variables: struct{}{}, } // Serialize the GraphQLRequest object to JSON jsonData, err := json.Marshal(graphQLRequest) if err != nil { fmt.Println(\u0026#34;Error marshaling JSON:\u0026#34;, err) return } payload := strings.NewReader(string(jsonData)) client := \u0026amp;http.Client{} req, err := http.NewRequest(method, url, payload) if err != nil { fmt.Println(err) return } req.Header.Add(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) req.Header.Add(\u0026#34;Authorization\u0026#34;, fmt.Sprintf(\u0026#34;Token %s\u0026#34;, nautobotToken)) res, err := client.Do(req) if err != nil { fmt.Println(err) return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Println(err) return } // Parse the response JSON var ipResponse IPAddressResponse err = json.Unmarshal(body, \u0026amp;ipResponse) if err != nil { fmt.Println(\u0026#34;Error parsing response JSON:\u0026#34;, err) return } // Access the IP address from the response ipAddresses := ipResponse.Data.IPAddresses if len(ipAddresses) \u0026gt; 0 { ipAddress := ipAddresses[0].Address fmt.Println(\u0026#34;IP Address:\u0026#34;, ipAddress) } else { fmt.Println(\u0026#34;No IP address found.\u0026#34;) } } pynautobot #Pynautobot also provides a helper method to be able to make GraphQL queries as well. It returns a JSON object at .json and can be accessed as a dictionary:\n1 2 3 4 5 6 7 8 9 10 11 12 import os import pynautobot query_str = \u0026#34;\u0026#34;\u0026#34; query { ip_addresses(device:\u0026#34;bre01-edge-01\u0026#34;, interface: \u0026#34;Loopback0\u0026#34;) { address } }\u0026#34;\u0026#34;\u0026#34; nautobot = pynautobot.api(url=os.getenv(\u0026#34;NAUTOBOT_URL\u0026#34;), token=os.getenv(\u0026#34;NAUTOBOT_TOKEN\u0026#34;)) print(nautobot.graphql.query(query=query_str).json[\u0026#34;data\u0026#34;][\u0026#34;ip_addresses\u0026#34;][0][\u0026#34;address\u0026#34;]) GraphQL: Ansible #A slight modification to the Ansible playbook, adding a variable at the top for the query string, and using the action module to query instead of a lookup plugin.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 --- - name: \u0026#34;GET IP ADDRESS FROM NAUTOBOT\u0026#34; hosts: localhost connection: local gather_facts: no vars: nautobot_url: \u0026#34;{{ lookup(\u0026#39;ansible.builtin.env\u0026#39;, \u0026#39;NAUTOBOT_URL\u0026#39;) }}\u0026#34; nautobot_token: \u0026#34;{{ lookup(\u0026#39;ansible.builtin.env\u0026#39;, \u0026#39;NAUTOBOT_TOKEN\u0026#39;) }}\u0026#34; query_str: | query { ip_addresses(device:\u0026#34;bre01-edge-01\u0026#34;, interface: \u0026#34;Loopback0\u0026#34;) { address } } tasks: - name: \u0026#34;10: GET IP ADDRESS FROM NAUTOBOT\u0026#34; networktocode.nautobot.query_graphql: url: \u0026#34;{{ nautobot_url }}\u0026#34; token: \u0026#34;{{ nautobot_token }}\u0026#34; query: \u0026#34;{{ query_str }}\u0026#34; register: \u0026#34;query_response\u0026#34; - debug: msg: \u0026#34;{{ query_response[\u0026#39;data\u0026#39;][\u0026#39;ip_addresses\u0026#39;][0][\u0026#39;address\u0026#39;] }}\u0026#34; With the expected result as seen:\nPLAY [GET IP ADDRESS FROM NAUTOBOT] ******************************************************************************************************** TASK [10: GET IP ADDRESS FROM NAUTOBOT] **************************************************************************************************** ok: [localhost] TASK [debug] ******************************************************************************************************************************* ok: [localhost] =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;10.30.128.1/32\u0026#34; } PLAY RECAP ********************************************************************************************************************************* localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Summary #There are several ways that you can get IP addresses out of Nautobot. With having the robust API capabilities that Nautobot has, including the GraphQL endpoints, you are able to work quite quickly to get at the data that you need to automate your network, automate your enterprise. Whether it is working with some programming languages, or working with an automation engine such as Ansible, Nautobot is available to help out!\n","date":"2023-07-07","permalink":"https://josh-v.com/nautobot-get-ip-address-info/","section":"Posts","summary":"\u003cp\u003eOne of Nautobot\u0026rsquo;s primary functions is to serve as an IPAM solution. Within that realm, the application needs to provide a method to get at IP address data for a device, quickly and easily. In this post I will review three prominent methods to get an IP address from Nautobot. It will demonstrate getting the address via:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eNautobot REST API\n\u003cul\u003e\n\u003cli\u003ecurl\u003c/li\u003e\n\u003cli\u003ePython Requests\u003c/li\u003e\n\u003cli\u003eGoLang HTTP\u003c/li\u003e\n\u003cli\u003epynautobot\u003c/li\u003e\n\u003cli\u003eAnsible Lookup\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eNautobot GraphQL API\n\u003cul\u003e\n\u003cli\u003ecurl\u003c/li\u003e\n\u003cli\u003ePython Requests\u003c/li\u003e\n\u003cli\u003eGoLang HTTP\u003c/li\u003e\n\u003cli\u003epynautobot\u003c/li\u003e\n\u003cli\u003eAnsible Lookup\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eEach method I will demonstrate how to get the IP address for Loopback0 on the \u003ca href=\"https://demo.nautobot.com/dcim/devices/5e7c0bdd-254b-44cb-bf7c-2f2560082f6d/?tab=main\" target=\"_blank\" rel=\"noreferrer\"\u003edevice\u003c/a\u003e \u003ccode\u003ebre01-edge-01\u003c/code\u003e within the demo instance of Nautobot. This device has 62 interfaces, so being able to filter down to which interface IP address we are looking for makes sense.\u003c/p\u003e","title":"Nautobot: Get IP Addresses From Nautobot"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/flask/","section":"Tags","summary":"","title":"Flask"},{"content":"In this post I\u0026rsquo;m going to dive into a bit more on the Nautobot custom validators. This is a powerful validation tool that will allow for you to write your own validation capability, including in this demonstration on how to complete a validation against a remote API endpoint. The custom validators are a part of the Nautobot App extension capability. This allows for custom code to be written to validate data upon the clean() method being called, which is used in the majority of API calls and form inputs of Nautobot.\nI will look to accomplish four different objectives in this post from my point of view. This will help to get some targeted experiences with what I believe the DevNet Expert exam has in mind for Web Services, working with Flask. In my day to day I deal more so within the Django Web Framework to build web applications that are part of the Nautobot ecosystem. So the need to write some Flask applications is a good way to branch out some.\nThe goals that I have for this post:\nCreating a Web Services endpoint using Flask Creating multiple endpoints for validation Process the HTTP Request Provide a response Provide additional details around the Nautobot custom validation engine Nautobot Custom Validators #The Nautobot extensibility features are quite awesome. It is what makes Nautobot a platform worth investing in. The Nautobot Custom Validators is no exception here. The example from the link previous is that a validator may set the requirement that every site must have a region:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # custom_validators.py from nautobot.apps.models import CustomValidator class SiteValidator(CustomValidator): \u0026#34;\u0026#34;\u0026#34;Custom validator for Sites to enforce that they must have a Region.\u0026#34;\u0026#34;\u0026#34; model = \u0026#39;dcim.site\u0026#39; def clean(self): if self.context[\u0026#39;object\u0026#39;].region is None: # Enforce that all sites must be assigned to a region self.validation_error({ \u0026#34;region\u0026#34;: \u0026#34;All sites must be assigned to a region\u0026#34; }) custom_validators = [SiteValidator] More information about the details of this can be found on the link provided, including the most up to date information on writing custom validators.\nFlask App #First the Flask application. In this instance, I am using the Flask extension Flask-RESTX to help in handling a REST API endpoint that I plan on extending in future iterations. After installing Flask and Flask-RESTX into a new Poetry virtual environment I built the following API endpoint that will check that the hostname is all lower case:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 from flask import Flask from flask_restx import Api, Resource app = Flask(__name__) api = Api(app) @api.route(\u0026#34;/validate_name/\u0026#34;) class HelloWorld(Resource): def post(self): \u0026#34;\u0026#34;\u0026#34;Handles POST request. Data comes in via api.payload object that can be interacted upon. This is a type dictionary. \u0026#34;\u0026#34;\u0026#34; # Get the data that is coming in, expecting the key of proposed_device_name proposed_device_name = api.payload.get(\u0026#34;proposed_name\u0026#34;) print(proposed_device_name) print(api.payload) if proposed_device_name is None: return { \u0026#34;valid\u0026#34;: False, \u0026#34;details\u0026#34;: \u0026#34;Proposed Name was not sent appropriately\u0026#34;, }, 422 # Verify the case if proposed_device_name != proposed_device_name.lower(): return { \u0026#34;valid\u0026#34;: False, \u0026#34;details\u0026#34;: f\u0026#34;{proposed_device_name} is not all lower case.\u0026#34;, }, 201 return { \u0026#34;valid\u0026#34;: True, \u0026#34;details\u0026#34;: f\u0026#34;{proposed_device_name} passes validation\u0026#34;, }, 201 if __name__ == \u0026#34;__main__\u0026#34;: app.run(debug=True) Lines 17 \u0026amp; 18 are just the debugging code that will print out to the console the details to help understand what is coming in. Testing Flask Endpoints (Manual) #Some manual tests of the Flask endpoint to verify that the data is working as expected get the following results.\nGood Test #curl localhost:5000/validate_name/ -d \u0026#39;{\u0026#34;proposed_name\u0026#34;:\u0026#34;goodname\u0026#34;}\u0026#39; -X POST -H \u0026#34;Content-Type: application/json\u0026#34; The response:\n{ \u0026quot;valid\u0026quot;: true, \u0026quot;details\u0026quot;: \u0026quot;goodname passes validation\u0026quot; } Bad Test #curl localhost:5000/validate_name/ -d \u0026#39;{\u0026#34;proposed_name\u0026#34;:\u0026#34;Badname01\u0026#34;}\u0026#39; -X POST -H \u0026#34;Content-Type: application/json\u0026#34; { \u0026quot;valid\u0026quot;: false, \u0026quot;details\u0026quot;: \u0026quot;Badname01 is not all lower case.\u0026quot; } Returns #In working with an endpoint, it is imperative from a backwards compatibility perspective that you maintain the keys that you start with. If you are adopting a micro services approach like this with an endpoint that is going to provide data back, then you must maintain some consistency with the keys that you are using. If you all of a sudden change \u0026ldquo;valid\u0026rdquo; to \u0026ldquo;status\u0026rdquo;, then you are going to have extra work to do. Maintain the keys, and maybe expand the data structure to maintain \u0026ldquo;valid\u0026rdquo; as a key. You can add more keys into the base of the structure response.\nNautobot Custom Validator # I have a \u0026ldquo;sandbox\u0026rdquo; Nautobot App that I use to do tests like this. It is its own standalone plugin that I have built from the structure that built many of the Network to Code sponsored Nautobot Apps. You may want to build a Sandbox app for yourself to work from as well. The Nautobot Custom Validator is quite boring actually compared to the code that is put into the remote API. The code is the following and then I\u0026rsquo;ll explain a few of the core parts:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 \u0026#34;\u0026#34;\u0026#34;Custom validators.\u0026#34;\u0026#34;\u0026#34; import requests from nautobot.apps.models import CustomValidator class DeviceValidator(CustomValidator): \u0026#34;\u0026#34;\u0026#34;Custom validator for Device names to be validated remotely.\u0026#34;\u0026#34;\u0026#34; model = \u0026#34;dcim.device\u0026#34; def clean(self): response = requests.post( url=\u0026#34;http://validator_svc/validate_name/\u0026#34;, json={\u0026#34;proposed_name\u0026#34;: self.context[\u0026#34;object\u0026#34;].name}, headers={\u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;}, ) if not response.json().get(\u0026#34;valid\u0026#34;): self.validation_error({\u0026#34;name\u0026#34;: response.json().get(\u0026#34;details\u0026#34;, \u0026#34;Error in testing.\u0026#34;)}) custom_validators = [DeviceValidator] The validator is going to make an API request out and Python Requests is the best library for single threaded requests, so that is imported. If you do not have the Requests module in your Nautobot environment, you need to make sure to add the Python Requests library to the pip install in the Nautobot environment.\nLine 10 shows the model that is having the validation applied to. This is required for the application to know what to validate.\nWithin the clean method the first action is to make a POST request to the validator service. This provides a response back as part of the API definition that was set up.\nLines 19 and 20 are the components that are required by Nautobot and the custom validator to provide a negative response if there is an error in the validation and to provide a message back. The dictionary key is the field on the form that is having an error. The value in the dictionary is the details to present back to the form.\nThe last line on line 23 assigns a list of the single class to do validation against. This then gets loaded into the validation engine for Nautobot.\nHow Would I Do This Differently? #If all I was doing was using case or another field that could be done with Regex, I would look at using the Nautobot Data Validation app. That was built with this in mind and to provide a methodology. The second part I would do would be to not put the validation into a separate application, at least not immediately. With the minimal amount of code, that is something that could have just been done in the validator itself. I completed it this way for demonstration purposes and a start for some personal learning of Flask for my own studies.\nSummary #All together, I\u0026rsquo;m looking at having the Nautobot custom validator as part of my practices as it is needed, but is something that should probably be added sooner than later.\nTo get started with a custom validator for your business logic, create your own Nautobot App, then install it into your environment. You don\u0026rsquo;t need to have new models in order to make a Nautobot App. The best way to do this at the moment is to copy another plugin, such as the Nautobot BGP Plugin. Remove all of the information in the models and then put the code pieces together. That is not a great answer, and there will be more coming soon on this!\nJosh\n","date":"2023-07-05","permalink":"https://josh-v.com/nautobot-remote-validation/","section":"Posts","summary":"\u003cp\u003eIn this post I\u0026rsquo;m going to dive into a bit more on the Nautobot custom validators. This is a powerful validation tool that will allow for you to write your own validation capability, including in this demonstration on how to complete a validation against a remote API endpoint. The custom validators are a part of the Nautobot App extension capability. This allows for custom code to be written to validate data upon the \u003ccode\u003eclean()\u003c/code\u003e method being called, which is used in the majority of API calls and form inputs of Nautobot.\u003c/p\u003e","title":"Nautobot Remote Validation"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/security/","section":"Tags","summary":"","title":"Security"},{"content":" Hot Take: Newly released (at some point anyway) is Slack Canvas, what looks to be a little bit of on demand wiki, collaboration space, and possibly (based on marketing materials) workflow organizer. This came to light as a \u0026ldquo;pop up\u0026rdquo; when I went into a Slack window on my mobile. Being a curious person and someone that is willing to try out new things I jumped right in.\nThe Take #Without reading any marketing pages or anything else, I just started to do a few things with it. The first look at the canvas I immediately attempted to write with Markdown. I instinctively tried making some headings, and it actually worked, I was very surprised as I would have thought that the canvas would remain a very Slack flavor of Markdown, which hasn\u0026rsquo;t really worked in the past. But the headings came right in and it even provides a collapsible menu for the headings.\nNext up was to try to make bold, italic, and strikethrough formatting. It followed the Slack flavor Markdown here. So that will continue to be a switch up from the standard GitHub flavored Markdown that so many are accustomed to.\nOne of the features that I\u0026rsquo;m liking is the ability to have quick access to comments within the Canvas for any section, paragraph, item added. This seems very natural.\nI then went and checked out the marketing material around the feature, take a look for yourself here. I then wrote the majority of this blog post and looked at the getting started. The following quote kind of concerns me a small bit:\nAll existing posts will eventually be converted to canvases, and all of your content will be saved. On free teams, your posts will be converted to read-only canvases. You can continue to edit your existing posts until they are converted.\nIf I\u0026rsquo;m reading the block quote there properly, all posts are going to move to a Canvas for each message? I guess time will tell here.\nConfluence? #This is all giving a bit of feel to having Confluence pages. Where you are able to collaborate in real time with others, leave comments, and make for good pages that can be consumed in a Wiki format. The difficult thing with Confluence is that it is a bit clunky to go between pages. You are relying on the web page loading, which has some overhead. The speed at which Canvas pages are able to load and then render text is pretty smooth within the Slack application itself.\nDown Sides? # Yet another place to store wiki like content Escape Key does not close out the Canvas window. Am I Going to Use It? #I\u0026rsquo;m going to give it a try. I like having it in the messaging app, almost the new universal user interface. I believe in this concept of having work within collaboration/messaging apps. ChatOps is another example within Slack that is providing network automation to users. Everyone is in messaging apps all day, every day. So this is a good feel to have.\nThoughts on Slack changes; maybe thoughts on having my hot takes on these? Let me know.\nJosh\n","date":"2023-07-05","permalink":"https://josh-v.com/slack-canvas/","section":"Posts","summary":"\u003cspan class=\"flex\"\u003e\n  \u003cspan\n    class=\"ms-1 rounded-md border border-primary-400 px-1 py-[1px] text-xs font-normal text-primary-700 dark:border-primary-600 dark:text-primary-400\"\n  \u003e\n    \nHot Take:\n\n  \u003c/span\u003e\n\u003c/span\u003e\n\n\n\u003cp\u003eNewly released (at some point anyway) is Slack Canvas, what looks to be a little bit of on demand wiki, collaboration space, and possibly (based on marketing materials) workflow organizer. This came to light as a \u0026ldquo;pop up\u0026rdquo; when I went into a Slack window on my mobile. Being a curious person and someone that is willing to try out new things I jumped right in.\u003c/p\u003e","title":"Slack Canvas"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/poetry/","section":"Tags","summary":"","title":"Poetry"},{"content":"The Python Poetry is our go to package management system thus far, you can see that in all of the Python projects that Network to Code open sources, such as Nautobot, pyntc, network-importer, and NTC-Templates. Lately though, I\u0026rsquo;ve been having some challenges when my HomeBrew updates happen and my system Python gets updated. I\u0026rsquo;ve been able to recover with the help of the same few pages I land on from my Google searches. But since I\u0026rsquo;ve done this twice now, I\u0026rsquo;m using this post to document the fix as much as for myself, but for anyone else that may come across Poetry issues.\nThe Issue #The issue occurs when I\u0026rsquo;m updating the system Python through HomeBrew. I start to see errors where Poetry is unable to be detected, with some nasty looking MacOS shell issues. Such as:\npoetry Library not loaded: /opt/homebrew/Cellar/python@3.10/3.10.12/Frameworks/Python.framework/Versions/3.10/Python With this error, I made the mistake (maybe) of just removing poetry by finding the location with which poetry and then removing that file. Not recommended.\nThe Fix #The proper way that I\u0026rsquo;m finding to fix this is to run the Poetry uninstall scripts, which are found on the Poetry docs.\ncurl -sSL https://install.python-poetry.org | python3 - --uninstall curl -sSL https://install.python-poetry.org | POETRY_UNINSTALL=1 python3 - Once it is removed, you try to re-install Poetry, but then you get the following symlinks issue:\nraise Exception(\u0026#34;This build of python cannot create venvs without using symlinks\u0026#34;) Exception: This build of python cannot create venvs without using symlinks This is coming from a pyenv set up that I\u0026rsquo;ve also been convinced to run (however, I\u0026rsquo;m not convinced). So the next step is instead of installing to python3, you install to the current minor version of Python:\ncurl -sSL https://install.python-poetry.org | python3.10 - My Preferred Set Up For Various Versions of Python #My preferred method of running different versions of Python is the use of containers. Then you have a fully isolated system rather than running different versions within your shell. Allow the system Python to be that, the system. Then use containers to handle the different versions.\nSummary #This is my fix thus far that I have found. If there is a better way of getting Poetry to work with pyenv (until I uninstall it), I\u0026rsquo;d love to hear what the solutions are. Until next time, happy automating!\nJosh\n","date":"2023-06-29","permalink":"https://josh-v.com/nornir-brief/","section":"Posts","summary":"\u003cp\u003eThe Python \u003ca href=\"https://python-poetry.org/\" target=\"_blank\" rel=\"noreferrer\"\u003ePoetry\u003c/a\u003e is our go to package management system thus far, you can see that in all of the Python projects that Network to Code open sources, such as \u003ca href=\"https://docs.nautobot.com/projects/core/en/stable/\" target=\"_blank\" rel=\"noreferrer\"\u003eNautobot\u003c/a\u003e, \u003ca href=\"https://pyntc.readthedocs.io/en/latest/user/lib_overview/\" target=\"_blank\" rel=\"noreferrer\"\u003epyntc\u003c/a\u003e, \u003ca href=\"https://github.com/networktocode/network-importer\" target=\"_blank\" rel=\"noreferrer\"\u003enetwork-importer\u003c/a\u003e, and \u003ca href=\"https://github.com/networktocode/ntc-templates\" target=\"_blank\" rel=\"noreferrer\"\u003eNTC-Templates\u003c/a\u003e. Lately though, I\u0026rsquo;ve been having some challenges when my HomeBrew updates happen and my system Python gets updated. I\u0026rsquo;ve been able to recover with the help of the same few pages I land on from my Google searches. But since I\u0026rsquo;ve done this twice now, I\u0026rsquo;m using this post to document the fix as much as for myself, but for anyone else that may come across Poetry issues.\u003c/p\u003e","title":"Poetry Fix"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/programming/","section":"Tags","summary":"","title":"Programming"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/hashicorp/","section":"Tags","summary":"","title":"Hashicorp"},{"content":"With Nautobot, one of the things that came up was how to work with secrets. Nautobot itself is not the place to maintain secrets, as it is not a vault. There may be some good cryptographic libraries out to handle this, but by its nature, that is not the intent. So Nautobot has written methods to be able to retrieve secrets from proper vault sources and be able to leverage them. These can be tricky to get set up however. I had struggled for a while myself. So now that I have it working, I thought it would be a good time to have a quick personal blog about it.\nSecrets Set Up #In my writing within the Open Source Network Management book I showed how to get started with Hashicorp Vault for secrets within Ansible. This is a natural progression to house my secrets for my Nautobot recommendation as well. This will not cover the set up of Nautobot for secrets. That is best to be done by the provider documentation. This will however dive into how the vault is set up in my environment, and how that translates into Nautobot.\nNautobot Parameters #When setting up a Hashicorp Vault secret in Nautobot, you will need the following parameters:\nPath Key Mount point Kv version Nautobot Parameters: Vault Mount Point #So the vault that I have set up inside of Vault, is a KV store. In the image there is the cubbyhole which is used for local passwords, so I wouldn\u0026rsquo;t use that for my vaults. The name of kv below will be the first item into the Nautobot secret and will map to the Mount point with Nautobot. The default is secret, and this needs to change to kv if that is what you have.\nNautobot Parameters: Vault Path #Next down the folder path on Hashicorp Vault is the folder/path. When you navigate into the kv link you then get the next item of the path, in this case net_device. This is the Path within the parameters form.\nNautobot Parameters: Key #The last part you need within your Nautobot secret is the Key itself. So in the secret within Vault, you have various keys. These line up with the key within the parameters of the Nautobot secret.\nIt\u0026rsquo;s worth noting that each key in the vault secret is its own entity. That being that you will need to set up a Nautobot Secrets Group to pair the username and password together. You need to set up a secret for both a username and password if you are having a username/password combination that is often the case. Testing the Secret #One of my favorite features of the Nautobot Secrets Provider is that there is the opportunity to \u0026ldquo;test\u0026rdquo; the secrets gathering. Once the secret is created you can find the Check Secret button in the upper right.\nWhen you click that button and all is well you get the successful message back.\nAlignment Table #Here is what the Nautobot terminology lined up:\nNautobot Parameter Hashicorp Vault Path Folders inside of the KV store, including the secret name itself Key Key within the secret Mount point Name of the KV store Kv version Version of the Key/Value store Summary #Nautobot is handling secrets right in my opinion. That secrets are not to be stored within Nautobot. These are things that there are great solutions already available for. Use those tools. Nautobot provides a good mechanism to be able to integrate with various secrets providers. This hopefully helps out someone get started with using Hashicorp Vault secrets and Nautobot. I know I will be using this post again in the future some day!\nLet me know on social media (until I get a comment system built in) what your thoughts are.\nJosh\n","date":"2023-06-27","permalink":"https://josh-v.com/nautobot-secrets-hashicorp-vault/","section":"Posts","summary":"\u003cp\u003eWith Nautobot, one of the things that came up was how to work with secrets. Nautobot itself is not the place to maintain secrets, as it is not a vault. There may be some good cryptographic libraries out to handle this, but by its nature, that is not the intent. So Nautobot has written methods to be able to retrieve secrets from proper vault sources and be able to leverage them. These can be tricky to get set up however. I had struggled for a while myself. So now that I have it working, I thought it would be a good time to have a quick personal blog about it.\u003c/p\u003e","title":"Nautobot Secrets - Hashicorp Vault"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/vault/","section":"Tags","summary":"","title":"Vault"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/cisco/","section":"Tags","summary":"","title":"Cisco"},{"content":"Today I was working to demonstrate how to get started with Nautobot Jobs within the Jobs root of Nautobot. This is not a pattern that I develop often, as I am typically developing Jobs within a plugin as my development standard. More to come on that later. During this case, the ask was to build a Job that would connect to a network device. I had a few troubles that I didn\u0026rsquo;t want to have to work through on a call that had limited time and that was a screen share. So I am taking to working on this via a blog post to share, and hopefully will be helpful for others as well.\nNautobot Documentation #The Nautobot documentation is pretty straight on. But it has grown organically over time. As such, this is going to be a small walkthrough in an opinionated way. This post will dive into:\nProviding a Job form that will allow you to select a Device Connect to the device Execute a show version on the device as a method of connecting to the device In this case I will be connecting to a Cisco device over SSH with Netmiko.\nCreating the Job #The default Jobs root path is $NAUTOBOT_ROOT/jobs/, which following the default install instructions for Nautobot is then /opt/nautobot/jobs/ on a virtual machine install. With the default install the jobs directory comes with an __init__.py file:\nnautobot@nautobot-host:~/jobs$ tree . └── __init__.py Let\u0026rsquo;s build out the structure. The Nautobot repository has an Example file (or use this as a starting point) to get started. In the repository there is an example plugin that has jobs in it as a reference. You can find the jobs information then here https://github.com/nautobot/nautobot/blob/develop/examples/example_plugin/example_plugin/jobs.py.\nHere I\u0026rsquo;m executing vim demo_jobs.py from the /opt/nautobot/jobs/ directory. In the end, the file just needs to be in the directory.\nThis will be a multiple step process. I like to debug the job along the way in order to know what I am working with. So the first iteration I include the following code to verify what is being sent in from the device form. Here is what the code looks like.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 from django.conf import settings # Importing Nautobot DCIM device model in order to be able to choose what device I want to connect to from nautobot.dcim.models import Device # The import of Job is needed to inherit the Job class. This is the magic sauce for Nautobot Jobs. # ObjectVar import will be used to select a device from nautobot.extras.jobs import Job, ObjectVar # Import Netmiko to connect to the device and execute commands from netmiko import ConnectHandler # Setting the name here gives a category for these jobs to be categorized into name = \u0026#34;josh-v.com Demo jobs\u0026#34; class GetShowVersion(Job): device = ObjectVar( model=Device, # Using the Device model imported to say I want to select devices query_params={ # Using this as a method to make sure that the device has a primary IP address \u0026#34;has_primary_ip\u0026#34;: True, \u0026#34;status\u0026#34;: \u0026#34;active\u0026#34;, # Used to make sure that the device is active } ) # I like to describe the class Meta as what information about the Job to pass into Nautobot to help describe the Job class Meta: name = \u0026#34;Get show version\u0026#34; description = \u0026#34;Get the version information from a device\u0026#34; # Define task queues that this can run in. task_queues = [ settings.CELERY_TASK_DEFAULT_QUEUE, \u0026#34;priority\u0026#34;, \u0026#34;bulk\u0026#34;, ] # The code execution, all things for the job are here. def run(self, data, commit): device = data[\u0026#34;device\u0026#34;] self.log_debug(device) jobs = (GetShowVersion) At this point, you can exit out and then execute nautobot-server post_upgrade and restart the services sudo systemctl restart nautobot nautobot-worker nautobot-scheduler from the server CLI (usually not the Nautobot user). During this process you should see the message:\n01:49:20.083 INFO nautobot.extras.utils : Created Job \u0026#34;josh-v.com Demo jobs: Get show version\u0026#34; from \u0026lt;local: GetShowVersion\u0026gt; 01:49:20.089 INFO nautobot.extras.utils : Refreshed Job \u0026#34;josh-v.com Demo jobs: Get show version\u0026#34; from \u0026lt;local: GetShowVersion\u0026gt; Then going into the UI menu of Jobs \u0026raquo;\u0026gt; Jobs you get the following result with the Job Enabled column having a red X on it.\nTo enable the Job for execution, take a look at the docs here on the Nautobot Docs page for enabling a Job.\nWhen you execute the Job for a device, you now get a basic \u0026ldquo;Hello World\u0026rdquo; execution. You can see the result with the device name displayed.\nGet the Device Information From Nautobot #The best way to get the information available within a Nautobot object is to work within the shell_plus environment. On your Nautobot server as the Nautobot user, enter the command nautobot-server shell_plus. This will bring you into an interactive shell, hopefully an iPython like environment. If you do not get an iPython shell and you are on a development host (not production), then you can do a pip install to get iPython installed (pip install ipython).\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 In [1]: mydevice = Device.objects.first() In [2]: mydevice.name Out[2]: \u0026#39;er01\u0026#39; In [3]: mydevice.platform Out[3]: \u0026lt;Platform: Cisco IOS\u0026gt; In [4]: mydevice.platform.name Out[4]: \u0026#39;Cisco IOS\u0026#39; In [5]: mydevice.platform.slug Out[5]: \u0026#39;cisco-ios\u0026#39; In [6]: mydevice.platform.napalm_driver Out[6]: \u0026#39;ios\u0026#39; In [7]: mydevice.primary_ip Out[7]: \u0026lt;IPAddress: 203.0.113.65/27\u0026gt; In [8]: str(mydevice.primary_ip) Out[8]: \u0026#39;203.0.113.65/27\u0026#39; In [9]: mydevice.primary_ip.host Out[9]: \u0026#39;203.0.113.65\u0026#39; In the exploration with the shell, on lines 3 and 4 you see the verification that I\u0026rsquo;m working with the particular device that I wanted to work with. Lines 6 and 7 show that the platform name will come out as Cisco IOS. This will not work for using Netmiko connection to the device, so I then went exploring further of the data. I then saw on lines 15 and 16 something that I can work with for Netmiko. So I am going to use the NAPALM driver. If you need to do some conversions of NAPALM drivers over to Netmiko, there are some mapping utilities in the NetUtils library that can help. Lastly I explored how to get the IP address that I wanted to connect to. mydevice.primary_ip is a Nautobot object. That cannot be used in its own to connect to the host. I checked the string representation of the object, but that doesn\u0026rsquo;t just get us the IP address. But I do know that the object has a separate host and mask_length objects. So I grabbed just the host for the Job.\nThe run method now looks like this:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import os # Used to convert NAPALM type to Netmiko type from netutils.lib_mapper import NAPALM_LIB_MAPPER # CODE OMITTED FOR BREVITY # # The code execution, all things for the job are here. def run(self, data, commit): device = data[\u0026#34;device\u0026#34;] self.log_debug(device.name) net_device_info = { \u0026#34;device_type\u0026#34;: NAPALM_LIB_MAPPER.get(device.platform.napalm_driver), \u0026#34;ip\u0026#34;: device.primary_ip.host, \u0026#34;username\u0026#34;: os.getenv(\u0026#34;NAUTOBOT_NAPALM_USERNAME\u0026#34;), \u0026#34;password\u0026#34;: os.getenv(\u0026#34;NAUTOBOT_NAPALM_PASSWORD\u0026#34;), } self.log_debug(net_device_info[\u0026#39;device_type\u0026#39;]) net_device = ConnectHandler(**net_device_info) output = net_device.send_command(\u0026#34;show version\u0026#34;) self.log_debug(output) On line 1 of the above example, the import os was added to get the environment variables to get the credential to connect to the device. This could be set, or you may want to use another method.\nThis is an example only. Environment variables have some considerations with that go along with them. There are also methods to work with the Nautobot Secrets providers that would be of better consideration here. But those were not added for the example of what it is that I\u0026rsquo;m trying to show in this post, how to connect and execute Netmiko Python scripts against a network device. The import on line 4 is used for converting the NAPALM platform into a usable Netmiko platform.\nWhen connecting to a network device in a multi-vendor environment there are a few considerations that need to be made. You need to know what the commands are needed to do various things. This is where the possibility of using NAPALM as a method to connect to devices and their use of \u0026ldquo;getters\u0026rdquo; would come in handy. This may be an idea for a future blog post. You can see the example on line 14 of the run method above the use of converting \u0026ldquo;ios\u0026rdquo; to \u0026ldquo;cisco_ios\u0026rdquo;.\nExecuting the Job #Now when you go to execute the job, you are able to get the show version output from the device to the screen. Take a look at the example:\nSummary #With Nautobot, there are many ways that you can get started with executing your own custom Jobs. By using Nautobot Jobs you can centralize your power scripts into one place, allowing for those power scripts to be put to use and then some by the entire organization, not just a select few. Take some design caution in what you do make available though. Nautobot centralizes, but also provides for authentication, user logging, and creates an API that is available for use to others. Looking for more on how the Jobs are an API endpoint, take a look at my previous post on creating that API endpoint.\nThanks for the read and Happy Automating!\nAppendix - Full Code #Here is the full code block:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import os from django.conf import settings # Importing Nautobot DCIM device model in order to be able to choose what device I want to connect to from nautobot.dcim.models import Device # The import of Job is needed to inherit the Job class. This is the magic sauce for Nautobot Jobs. # ObjectVar import will be used to select a device from nautobot.extras.jobs import Job, ObjectVar # Import Netmiko to connect to the device and execute commands from netmiko import ConnectHandler # Used to convert NAPALM type to Netmiko type from netutils.lib_mapper import NAPALM_LIB_MAPPER # Setting the name here gives a category for these jobs to be categorized into name = \u0026#34;josh-v.com Demo jobs\u0026#34; class GetShowVersion(Job): device = ObjectVar( model=Device, # Using the Device model imported to say I want to select devices query_params={ \u0026#34;has_primary_ip\u0026#34;: True, # Using this as a method to make sure that the device has a primary IP address \u0026#34;status\u0026#34;: \u0026#34;active\u0026#34;, # Used to make sure that the device is active } ) # I like to describe the class Meta as what information about the Job to pass into Nautobot to help describe the Job class Meta: name = \u0026#34;Get show version\u0026#34; description = \u0026#34;Get the version information from a device\u0026#34; # Define task queues that this can run in. task_queues = [ settings.CELERY_TASK_DEFAULT_QUEUE, \u0026#34;priority\u0026#34;, \u0026#34;bulk\u0026#34;, ] # The code execution, all things for the job are here. def run(self, data, commit): device = data[\u0026#34;device\u0026#34;] self.log_debug(device.name) net_device_info = { \u0026#34;device_type\u0026#34;: NAPALM_LIB_MAPPER.get(device.platform.napalm_driver), \u0026#34;ip\u0026#34;: device.primary_ip.host, \u0026#34;username\u0026#34;: os.getenv(\u0026#34;NAUTOBOT_NAPALM_USERNAME\u0026#34;), \u0026#34;password\u0026#34;: os.getenv(\u0026#34;NAUTOBOT_NAPALM_PASSWORD\u0026#34;), } self.log_debug(net_device_info[\u0026#39;device_type\u0026#39;]) net_device = ConnectHandler(**net_device_info) output = net_device.send_command(\u0026#34;show version\u0026#34;) self.log_debug(output) jobs = (GetShowVersion) ","date":"2023-06-17","permalink":"https://josh-v.com/nautobot-jobs-root-in-root/","section":"Posts","summary":"\u003cp\u003eToday I was working to demonstrate how to get started with Nautobot Jobs within the Jobs root of Nautobot. This is not a pattern that I develop often, as I am typically developing Jobs within a plugin as my development standard. More to come on that later. During this case, the ask was to build a Job that would connect to a network device. I had a few troubles that I didn\u0026rsquo;t want to have to work through on a call that had limited time and that was a screen share. So I am taking to working on this via a blog post to share, and hopefully will be helpful for others as well.\u003c/p\u003e","title":"Nautobot Jobs in Jobs Root"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/netmiko/","section":"Tags","summary":"","title":"Netmiko"},{"content":"In this post I dive into more about my migration of the blog site to Hugo static content system. I will dive into primarily the why and how during this post. This also dives into the few changes that I had to make in order to make the change over from a Jekyll site to the Hugo site.\nWhy Migrate? #While I do not have a ton of posts, I do have a few that I like to get out into the wild periodically. As I looked around the landscape of the blog pages these days, I was starting to see my page a bit dated. I had previously went with the Jekyll Minimal Mistakes theme. The theme itself was wonderful and quite extensible. However, one of my concerns besides the look was the lack of updates coming out on the theme. Not that I was going to be taking advantage of every new feature, there seems to have been a slow down.\nThe second reason is that I\u0026rsquo;ve been experimenting some more with GoLang and recently found a better understanding of how GoLang\u0026rsquo;s templating engine works. Although admittedly as I write this, I know that I will need to re-learn again this as I have not put it into practice, and it is not completely native in my mind. But knowing how the templating language works, will allow me to be able to be more extensible for the site in the future. And when comparing wanting to learn Ruby templating vs GoLang templating. GoLang is definitely further on the list.\nMigration Activities #To make the migration, there were several steps that I had to take and one optional step that I took along the way.\nMigrate Jekyll to Hugo repo There is a helpful Hugo migration script that will read a Jekyll blog organization and update the structure to Hugo There were a few minor challenges with the migration, that it didn\u0026rsquo;t take a few things into account that VS Code search was helpful for Update the existing markdown files for the new syntax Migration from GitLab Pages to GitHub Pages (Optional) Let\u0026rsquo;s go into a few of these topics.\nMigration from Jekyll to Hugo #I ended up doing a two parts on this. I followed the post here about doing so, which generally went well - https://chenhuijing.com/blog/migrating-from-jekyll-to-hugo/. I stopped after making the import to a repo named migrate. By doing this I had all of my data from the previous blog into a format for Hugo. On the repo that would become my GitHub Pages repository, I set up a new Hugo site using the hugo CLI tool. So that I would start fresh.\nMy fresh site I decided to go with the Congo theme. I had done some research on what themes were available and went with Congo for it\u0026rsquo;s freshness and sharpness to it. The documentation provided as well for each of the settings went deep. In checking the repository I also found that it was being actively maintained. I definitely liked the idea that it was being updated.\nBlog Posts - Base #From there I moved a few of the markdown files into the new repository and ran the command hugo server to get a local instance up and running. Taking a look at the posts, things started working very quickly. I was able to see my latest posts and was able to explore changes to the layout rapidly.\nBlog Posts - Images #One of the biggest changes for me is that I went ahead and moved the images from the assets directory in the Jekyll world into the static/images/ directory. This allowed me to change the URL path on the images to being /images/{{ imageFilePath/Name }}. Thankfully VS Code has a global find and replace that I was then able to replace all of the previous paths with that of the new path. And just like that the images were up and running.\nBlog Posts - Code Highlighting #Code highlighting was one of the bigger changes that I needed to accomplish in making the migration over from Jekyll. I had previously created some syntax highlighting with line numbers that were part of a Jekyll plugin. So my code looked like:\n{% highlight yaml linenos %} ...YAML HERE ... {% endhighlight %} I used VS Code\u0026rsquo;s Regex find and replace to help with this. I searched for:\n\\{% highlight (\\w+) linenos %\\} And replaced it with the following, without the spaces between the braces (I need to figure out the Hugo escape method):\n{ {\u0026lt; highlight $1 \u0026#34;linenos=table\u0026#34; \u0026gt;} } The endhighlight section was much easier. That was done with just a find and replace of finding {% endhighlight %} with the replacement of the { {\u0026lt; /endhighlight \u0026gt;} } command (again without the spaces between the braces).\nAt that point the code highlighting for multiple languages is complete.\nBlog Posts - Jinja Formatting #As part of the Jekyll formatting of the blog posts, the Ansible and Python Jinja formatting would get interpreted as a Jekyll template code. In order to get around that Jekyll had a {% raw %} with a corresponding {% endraw %} to allow for the Jinja formatting to show up. Well, now in Hugo and GoLang it would show up in the output. So this was a find and replace of both of those combinations and to remove them.\nMigration from GitLab Pages to GitHub Pages #Why I did this, this was more administrative than anything else. The rest of my world is done within GitHub with the open source projects that I work on. So this was a move that had a small bit of challenges. The primary piece was using GitHub Actions as the source of the GitHub Pages. I tried a few different combinations and in the end using the GitHub recommended Actions page when following the GitHub Pages set up, then GitHub Pages would get published.\nMy original reason for hosting on GitLab Pages was that GitLab Pages had supported HTTPS and custom domains. Both of these features have made their way into GitHub Pages at this point.\nSummary #So far I\u0026rsquo;m back pretty happy with the initial migration. The process is familiar and works well. The previewing capabilities are terrific. And I\u0026rsquo;m pretty happy with the cleaner interface.\nHappy automating!\nJosh\n","date":"2023-06-15","permalink":"https://josh-v.com/moving-to-hugo/","section":"Posts","summary":"\u003cp\u003eIn this post I dive into more about my migration of the blog site to Hugo static content system. I will dive into primarily the why and how during this post. This also dives into the few changes that I had to make in order to make the change over from a Jekyll site to the Hugo site.\u003c/p\u003e\n\u003ch2 id=\"why-migrate\" class=\"relative group\"\u003eWhy Migrate? \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#why-migrate\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eWhile I do not have a ton of posts, I do have a few that I like to get out into the wild periodically. As I looked around the landscape of the blog pages these days, I was starting to see my page a bit dated. I had previously went with the Jekyll Minimal Mistakes theme. The theme itself was wonderful and quite extensible. However, one of my concerns besides the look was the lack of updates coming out on the theme. Not that I was going to be taking advantage of every new feature, there seems to have been a slow down.\u003c/p\u003e","title":"Moving to Hugo"},{"content":"I\u0026rsquo;m a veteran Network Engineer who has finally caught on how to put together CS teachings with a Networking degree. Working on doing almost all things via automation.\nSelf re-taught network/automation engineer. I\u0026rsquo;ve primarily been digging around within Ansible and Python. I\u0026rsquo;ve been doing Python networking since 2015 when a speaker came in and talked about how he had put together a NOC for a conference heavily leveraging automation capabilities via Python. Since then I have been going deep down the rabbit hole that is Network Automation.\nIn 2020 Cisco released the Cisco DevNet certification program. I am one of the first 500 individuals to achieve a Cisco DevNet certification, with passing first the DevNet Associate exam. Later I followed up that certification with the DevNet Professional certification as well.\nMy Work #Books #During the pandemic, I thought what better way to get started than with writing a book about getting started with open source network tools. Get it on LeanPub. See the Open Source Network Management tab for more.\n2024 brought the release of two more books, published by Packt. The first released book Network Automation with Nautobot introduces how to automate networks with the use of Nautobot. See the tab linked for more.\nThe second book in 2024 is Modern Network Observability, which provides a hands-on approach to using open source tools for network observability in 2024.\nPodcasts # Packet Pushers - Ansible or Terraform Network Automation Nerds - Interview Part 1 Network Automation Nerds - Interview Part 2 Speaking Engagements # 2025-Q2 2025 MNNUG Spring Event At the MNNUG 2025 Spring event I was the keynote speaker talking about getting started with automation. Highlighting the journey that the source of truth has been on and what to look to do in order to get started with automating multi-vendor networks in an intelligent way. 2024-Q4 2024 Internet2 Technology Exchange At Internet2\u0026rsquo;s TechEx 2024 I talked alongside Shannon Byrnes about how to get started with Network Automation. Get going, and focus on what you can control. 2023-Q3 2023 Internet2 Technology Exchange At Internet2\u0026rsquo;s TechEx 2023 I gave an overview of Nautobot to the Network Automation community and introduced several of the core Nautobot Open Source Ecosystem Plugins such as Nautobot Golden Config, and the new configuration remediation capabilities built in. 2023-Q2 2023 WWT Automation Day - Minneapolis In this speaking engagement I had the opportunity to talk about Nautobot as the Open Source Network Automation and SOT applications. I gave an overview of what Nautobot brings to the table and incorporated demos of using real world scripts to complete activities leveraging the Nautobot open source ecosystem. 2023-Q2 2023 Summer MNNUG Introduction of Nautobot and the core functionality for the the audience. Drove attendance with the focus on Network Automation that saw individuals drive from 3 hours away! 2022-Q4 2022 NANOG86 In 2022 I had the opportunity to complete my first NANOG talk, on automating circuit maintenance notifications. This section will outline my continued source of talks:\nNANOG86 - Automating Circuit Maintenance Notifications 2021-Q1 2021 Ansible Meetup Ansible Records Ansible - Minneapolis Ansible Meetup January 2021 - Ansible Records Ansible - ARA 2020-Q3 2020 Interop I present on using CICD in a network automation capacity - Interop Network Automation CICD 2020-Q2 2020 Ansible Meetup Here I dive into using a SOT for automation combined with the use of Custom Filters - Minneapolis Ansible Meetup April 2020 Talk Ansible Content #In 2019 I worked to develop a Network Automation Course with Ansible for Packet Pushers. This was in my transition time to Network to Code. Note that the course was developed at a time when only NetBox was around. At this point in time I would swap out NetBox in favor of Nautobot.\nPacket Pushers Network Automation with Ansible: YouTube Network Automation Course (Packet Pushers): Ignition page. Minneapolis Ansible Meetup April 2020 Talk First half using NetBox with the NetBox Ansible Collections Second half on creating your own custom filters Ansible Guest Blog Post - Using NetBox as Ansible Source of Truth Minneapolis Ansible Meetup January 2021 - Ansible Records Telemetry Content # How to Introduce Telemetry Streaming (gNMI) in Your Network with SNMP with Telegraf Upcoming: DZone: How to Introduce Telemetry Streaming (gNMI) in Your Network with SNMP with Telegraf NTC Blog Posts I Wrote # Network Telemetry for SNMP Devices How to Monitor Your VPN Infrastructure with Netmiko, NTC-Templates, and a Time Series Database Monitor Your Network With gNMI, SNMP, and Grafana Monitoring Websites with Telegraf and Prometheus Alerting with Prometheus Cisco Champion #2021 - Cisco Champion\n","date":null,"permalink":"https://josh-v.com/about/","section":"Josh VanDeraa","summary":"\u003cp\u003eI\u0026rsquo;m a veteran Network Engineer who has finally caught on how to put together CS teachings with a Networking degree. Working on doing almost all things via automation.\u003c/p\u003e\n\u003cp\u003eSelf re-taught network/automation engineer. I\u0026rsquo;ve primarily been digging around within Ansible and Python. I\u0026rsquo;ve been doing Python networking since 2015 when a speaker came in and talked about how he had put together a NOC for a conference heavily leveraging automation capabilities via Python. Since then I have been going deep down the rabbit hole that is Network Automation.\u003c/p\u003e","title":"About"},{"content":"My Content on Other Sites #Using NetBox as a SoT for Ansible + Diving into Ansible Filters\nIn the video I go over NetBox as your Source of Truth, and walk one through getting started with using Ansible as that Source of Truth. In the later portion I go through using Ansible Filters to audit NetBox data to the device data.\nIntroduction to Ansible for Network Automation\nThis is a course that I created for Packet Pushers Ignition site. It is meant as real world introduction of Network Automation leveraging Ansilbe. The course takes you through many common tasks accomplished within Ansible, providing many examples.\nOnline Network Simulators (Free) #Online VIRL\nYouTube Video Introducing VIRL free\nPodcasts #The shows listed here are particular episodes that have resonated with me. I may be missing some, especially going back a few years as I just really started in on these podcasts lately even though they have been around for some time.\nZigbits Network Design #Main Page: Zigbits.tech\nCI/CD Podcast\nOne of my favorites on CI/CD, including definitions and examples.\nDesigning for DevOps\nNicholos Russo having a conversation around Designing for DevOps\nPacket Pushers #Building a Network Automation Framework - Ken Celenza A podcast about building automation frameworks. This is a long one, but excellent for what an automated environment will look like\nThe Source of Truth Shall Set You Free (To Automate)\nPodcast of using Netbox, Grafana, Influx, and a modern company system\nAutomation/DevOps #Blog Sites #Byrn Baker\nNick Russo - @nickrusso42518\nKatherine McNamara - @kmcnam1\nJohn Capobiano\nJason Edelman\nKen Celenza\nScott Lowe\nLinks to checkout further #Building Dynamic Documentation Using Parser\nGenie Parsers\nJSON/YAML #David Barroso - JSON in Flat Format\nAnsible #Pinakes - Self Service portal\nEcoSystem Homepage\nNetwork CLI Connection Types\nAnsible Lint Github Link\nKen\u0026rsquo;s List of Links\nIPvSean IP Address Filter\nCisco pyATS\nAnsible Tools\nASA for Vagrant\nAnsible 2.5 Networking Changes\nCopy SSH Keys, format of playbook, etc.\nGuy who wrote a book on dev ops\nJeff Geerling - Ansible with Win10\nSave IOS configs with Ansible\nAnsible Network Automation Github Page\nCreate a list from output of Ansible\nYAML Guide 1\nCoding Packets Ansible\nMonitoring and Telemetry Links #Panos2Grafana\nPanograf\nAPC UPS Monitoring\nPython #Real Python Code Quality\nNornir #TBD, content coming soon!\nJenkins #Setting up Ansible in Jenkins\nHelpful Tools #Template Rendering Online Tester\nPiKVM\nProxmox Kubernetes Environment\nDatabase Tools #DBeaver - Thanks @itdependsnet\nTechnology Overviews #Ansible Networking Github Page\nVxLAN Overview 1\nCisco DC Spine \u0026amp; Leaf Design Overview WhitePaper\nCisco Security Events - Syslog\nIvan BGP in EVPN\nSpanning Tree: Nexus 5k Guide\nLower end Cat Switches\nGuide to better SSH Security\nVSS Main\nCumulus Comparing Commands\nTelemetry #Cisco Getting Started with Streaming Telemetry\nCisco Scaling Telemetry with IOS-XR \u0026amp; InfluxData\nGeneric Hardware Links #USB-C 4k video explained\nMac Hacks #macOS Setup Guide - sourabhajaj\nNew Tech #GNS3 Tech #GNS3 Automation #NetPanda Ansible+GNS3\nDMVPN #GNS3 DMVPN Intro\nGNS3 Learning Lab - Python for Network Engineers $$$$\nGreg Mueller Youtube Series\nVxLAN #VxLAN GNS3 CSR1000V OSPF\nVxLAN Overview Video\nAnsible PB to push VNIs\nVagrant #Getting started with Network Vagrant Images\nProgramming Nuggets #Netmiko #Kirk Byers Home Page\nLoading base configurations into VIRL Images (GNS3)\nTheading with Netmiko Example\nYANG Modeling #Cisco Yang Develpment Kit - Github\nCisco YANG developemnt Kit Readthedocs\nPython #Python Bandit (Security)\nPython Cheat Sheets\nPython Switcher/Case Statement\nCheat Sheets #Markdown\nMarkdown Wordpress\nPacket Life Protocols Cheat Sheets\nOnline Subnet Calculator Page\nWeb Net Development #Django #This is the my original network development platform. I went with it due to challenges that Flask presented around getting it to look \u0026ldquo;Pretty\u0026rdquo; using Bootstrap. This looked like the right decision for the time. I would probably recommend Network Engineers using Flask for web development at this point.\nDjango LDAP Example\nFlask #This is a micro-services web setup. It comes very basic and is very easy to get moving. First recommendation - Flask Mega-Tuturial. It is awesome. Well worth the purchase.\nFlask Mega-Tutorial\nFlask LDAP\nGithub Pages Help #Original Github pages links\nOpen Source Projects #Fabio Load Balancer\nProduct Documentation Links #Cat9300\nCareer #PacketPushers: Portfolio\nHome Office Links #Great Priced Standing Desk\nRaspberry Pi Links #Setup as DNS Server\nRaw output of DNS Server Link\nGNS3 Tips #If looking to connect via Netmiko to GNS3, this may help:\nnet_connect = ConnectHandler( device_type=\u0026#39;generic_termserver_telnet\u0026#39;, # changed ip=\u0026#39;127.0.0.1\u0026#39;, username=\u0026#39;pyclass\u0026#39;, password=\u0026#39;cisco\u0026#39;, global_delay_factor=4, default_enter=\u0026#39;\\r\\n\u0026#39;, port=5000) Inspirational Links #Admiral McRaven @ UT\nAppendix #Sublime Text Editor Links #Download: Sublime Text Main Page\nSyncing\nMac: Map end/home keybindings\nSpace Gray Themes?\nOpen From Terminal with word Sublime\n","date":"2023-06-13","permalink":"https://josh-v.com/links/","section":"Josh VanDeraa","summary":"\u003ch2 id=\"my-content-on-other-sites\" class=\"relative group\"\u003eMy Content on Other Sites \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#my-content-on-other-sites\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003e\u003ca href=\"https://www.youtube.com/watch?v=GyQf5F0gr3w\" target=\"_blank\" rel=\"noreferrer\"\u003eUsing NetBox as a SoT for Ansible + Diving into Ansible Filters\u003c/a\u003e\u003cbr\u003e\nIn the video I go over NetBox as your Source of Truth, and walk one through getting started with\nusing Ansible as that Source of Truth. In the later portion I go through using Ansible Filters to\naudit NetBox data to the device data.\u003c/p\u003e","title":"Links"},{"content":"One of the great things about building an enterprise system, is being able to get systems to work cohesively amongst themselves to bring a complete solution. One of the workflows that is often required in a static IP address environment is the need to provide static IP addresses to hosts on a network segment. When using an IPAM (IP Address Management) solution such as Nautobot, the APIs and SDKs/modules made available for use in automation workflows is paramount to having the cohesion to make a seamless IT system.\nIn this post I will be diving into the use of Nautobot as the IPAM. Using Ansible and the Nautobot modules, I will then show how you can get the next available IP address and assign it for use to the next VM. There will likely need to be some minor tweaks for use in your system.\nNautobot Setup #The first action is to get Nautobot set up with the tags and prefixes that are going to be used. I am using a tag of VM Addresses to assign to Prefixes that are to be allowed to assign VM addresses to. This way, you can grab the specific prefix based on the Nautobot data, not being static. You may want to look at having some other tags as well and passing these items into the Ansible execution either from a UI Survey, ExtraVars, or having multiple instances of the playbook.\nThis playbook is also using Environment Variables of NAUTOBOT_URL and NAUTOBOT_TOKEN in order to be able to be ported to other Nautobot systems.\n--- # create_parent_prefix.yml - name: \u0026#34;SET UP PARENT PREFIX\u0026#34; hosts: localhost connection: local gather_facts: no vars: nautobot_url: \u0026#34;{{ lookup(\u0026#39;ansible.builtin.env\u0026#39;, \u0026#39;NAUTOBOT_URL\u0026#39;)}}\u0026#34; nautobot_token: \u0026#34;{{ lookup(\u0026#39;ansible.builtin.env\u0026#39;, \u0026#39;NAUTOBOT_TOKEN\u0026#39;)}}\u0026#34; tasks: - name: \u0026#34;100: CREATE A TAG FOR PARENT PREFIX OF VM ADDRESSING\u0026#34; networktocode.nautobot.tag: url: \u0026#34;{{ nautobot_url }}\u0026#34; token: \u0026#34;{{ nautobot_token }}\u0026#34; name: \u0026#34;VM Addresses\u0026#34; description: \u0026#34;Addresses for VMs to live in\u0026#34; - name: \u0026#34;200: SET UP PARENT PREFIXES FOR ALL REMOTE SITES\u0026#34; networktocode.nautobot.prefix: url: \u0026#34;{{ nautobot_url }}\u0026#34; token: \u0026#34;{{ nautobot_token }}\u0026#34; prefix: \u0026#34;{{ item }}\u0026#34; status: Active description: VM Addresses family: 4 state: present tags: - VM Addresses loop: - \u0026#34;198.51.100.0/30\u0026#34; - \u0026#34;203.0.113.0/30\u0026#34; - \u0026#34;198.51.100.192/26\u0026#34; The loop list could also become variables as well, which will allow even further dynamic capability. But this is just an example to do the work rather than having to set this in the UI and demo it. This set up method is much easier.\nProvisioning IP Addresses #Now that the Nautobot environment is set up with tags for the subnet, it is easy to write a playbook to get the prefixes that have the tag. Then from the prefixes that are available, check to see which prefix has available IP addresses. If there is one available, then go ahead and get that IP address and assign it for use.\nThis playbook is using GraphQL to query the data from Nautobot, this is the fastest way to gather data from Nautobot, using a filter on the prefixes tag vm-addresses. The vm-addresses is the slug of the Tag that is created in the first playbook execution.\n--- # get_and_set_next_available.yml - name: \u0026#34;GET AND USE NEXT AVAILABLE ADDRESS\u0026#34; hosts: localhost connection: local gather_facts: no vars: nautobot_url: \u0026#34;{{ lookup(\u0026#39;ansible.builtin.env\u0026#39;, \u0026#39;NAUTOBOT_URL\u0026#39;)}}\u0026#34; nautobot_token: \u0026#34;{{ lookup(\u0026#39;ansible.builtin.env\u0026#39;, \u0026#39;NAUTOBOT_TOKEN\u0026#39;)}}\u0026#34; found_available_address: False graphql_query: | { prefixes(tag: \u0026#34;vm-addresses\u0026#34;) { prefix id } } tasks: - name: \u0026#34;BLOCK: GET AND USE NEXT AVAILABLE IP, LIMIT TO SINGLE EXECUTION AT A TIME\u0026#34; block: - name: \u0026#34;100: GET PREFIXES AVAILABLE\u0026#34; networktocode.nautobot.query_graphql: url: \u0026#34;{{ nautobot_url }}\u0026#34; token: \u0026#34;{{ nautobot_token }}\u0026#34; query: \u0026#34;{{ graphql_query }}\u0026#34; register: query_response - debug: msg: \u0026#34;{{ query_response }}\u0026#34; - name: \u0026#34;200: FIND FIRST AVAILABLE IP ADDRESS\u0026#34; include_tasks: \u0026#34;find_available2.yml\u0026#34; loop: \u0026#34;{{ query_response[\u0026#39;data\u0026#39;][\u0026#39;prefixes\u0026#39;] }}\u0026#34; loop_control: index_var: \u0026#34;my_idx\u0026#34; when: not found_available_address Task 100 executes the GraphQL query to get the response. From here the data has just what we need to continue on.\nTask 200 executes the following tasks that are defined in the find_available.yml file. This is a separate file so that the group of tasks can be executed within a loop.\n# find_available.yml --- - name: \u0026#34;debug var\u0026#34; debug: msg: - \u0026#34;{{ my_idx }}: ID={{ item[\u0026#39;id\u0026#39;] }}, Prefix={{ item[\u0026#39;prefix\u0026#39;] }}\u0026#34; - \u0026#34;{{ found_available_address }}\u0026#34; - name: \u0026#34;300: GET NEXT AVAILABLE IP ADDRESS AND ASSIGN IT\u0026#34; networktocode.nautobot.ip_address: url: \u0026#34;{{ nautobot_url }}\u0026#34; token: \u0026#34;{{ nautobot_token }}\u0026#34; prefix: \u0026#34;{{ item[\u0026#39;prefix\u0026#39;] }}\u0026#34; status: \u0026#34;Active\u0026#34; state: new register: nautobot_ip_state when: \u0026#34;not found_available_address\u0026#34; # A second when check to not keep assigning the IP address - name: \u0026#34;310: ASSIGN found_available_address TO TRUE\u0026#34; ansible.builtin.set_fact: found_available_address: True when: nautobot_ip_state.changed Task 300 uses the Ansible module networktocode.nautobot.ip_address to get the next available address in the prefix and registers it to the variable nautobot_ip_state. In Task 310 the nautobot_ip_state is checked to determined if the IP address has changed. If it has, it will set the variable found_available_address to True, so that the system knows that it no longer needs to check for an address because an address has been found and set.\nThe looping condition from Task 200 to keep going through the playbook allows for more tasks to be added on after Task 200 and then the 300 series tasks. If this playbook were to be done at this point and nothing further is to be executed you could change task 310 to be an ansible.builtin.meta task with the directive of end_play to stop the looping and the Play itself. That is how you would look to chain multiple plays together in a Playbook, that the first play of finding the address is completed and the next play of adding the business logic would take place.\nBefore the first execution, here is what Nautobot looks like without an address assigned:\nThe first execution of the playbook you get the following result:\n❯ ansible-playbook get_and_set_next_available.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; PLAY [GET AND USE NEXT AVAILABLE ADDRESS] ****************************************************************************************************** TASK [100: GET PREFIXES AVAILABLE] ************************************************************************************************************* ok: [localhost] TASK [debug] *********************************************************************************************************************************** ok: [localhost] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;data\u0026#34;: { \u0026#34;prefixes\u0026#34;: [ { \u0026#34;id\u0026#34;: \u0026#34;819f71d5-d761-4011-a3d3-d61f9ea15fec\u0026#34;, \u0026#34;prefix\u0026#34;: \u0026#34;198.51.100.0/30\u0026#34; }, { \u0026#34;id\u0026#34;: \u0026#34;f23de4a0-7a5d-4921-9cbb-5f2da6eb419f\u0026#34;, \u0026#34;prefix\u0026#34;: \u0026#34;198.51.100.192/26\u0026#34; }, { \u0026#34;id\u0026#34;: \u0026#34;7c464ee3-1ce2-47c1-b1aa-5628ab421e83\u0026#34;, \u0026#34;prefix\u0026#34;: \u0026#34;203.0.113.0/30\u0026#34; } ] }, \u0026#34;failed\u0026#34;: false, \u0026#34;graph_variables\u0026#34;: null, \u0026#34;query\u0026#34;: \u0026#34;{\\n prefixes(tag: \\\u0026#34;vm-addresses\\\u0026#34;) {\\n prefix\\n id\\n }\\n}\\n\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://demo.nautobot.com\u0026#34; } } TASK [200: FIND FIRST AVAILABLE IP ADDRESS] **************************************************************************************************** included: ./automationday_demos/find_available.yml for localhost =\u0026gt; (item={\u0026#39;prefix\u0026#39;: \u0026#39;198.51.100.0/30\u0026#39;, \u0026#39;id\u0026#39;: \u0026#39;819f71d5-d761-4011-a3d3-d61f9ea15fec\u0026#39;}) included: ./automationday_demos/find_available.yml for localhost =\u0026gt; (item={\u0026#39;prefix\u0026#39;: \u0026#39;198.51.100.192/26\u0026#39;, \u0026#39;id\u0026#39;: \u0026#39;f23de4a0-7a5d-4921-9cbb-5f2da6eb419f\u0026#39;}) included: ./automationday_demos/find_available.yml for localhost =\u0026gt; (item={\u0026#39;prefix\u0026#39;: \u0026#39;203.0.113.0/30\u0026#39;, \u0026#39;id\u0026#39;: \u0026#39;7c464ee3-1ce2-47c1-b1aa-5628ab421e83\u0026#39;}) TASK [debug var] ******************************************************************************************************************************* ok: [localhost] =\u0026gt; { \u0026#34;msg\u0026#34;: [ \u0026#34;0: ID=819f71d5-d761-4011-a3d3-d61f9ea15fec, Prefix=198.51.100.0/30\u0026#34;, false ] } TASK [300: GET NEXT AVAILABLE IP ADDRESS AND ASSIGN IT] **************************************************************************************** ok: [localhost] TASK [310: ASSIGN found_available_address TO TRUE] ********************************************************************************************* skipping: [localhost] TASK [debug var] ******************************************************************************************************************************* ok: [localhost] =\u0026gt; { \u0026#34;msg\u0026#34;: [ \u0026#34;1: ID=f23de4a0-7a5d-4921-9cbb-5f2da6eb419f, Prefix=198.51.100.192/26\u0026#34;, false ] } TASK [300: GET NEXT AVAILABLE IP ADDRESS AND ASSIGN IT] **************************************************************************************** changed: [localhost] TASK [310: ASSIGN found_available_address TO TRUE] ********************************************************************************************* ok: [localhost] TASK [debug var] ******************************************************************************************************************************* ok: [localhost] =\u0026gt; { \u0026#34;msg\u0026#34;: [ \u0026#34;2: ID=7c464ee3-1ce2-47c1-b1aa-5628ab421e83, Prefix=203.0.113.0/30\u0026#34;, true ] } TASK [300: GET NEXT AVAILABLE IP ADDRESS AND ASSIGN IT] **************************************************************************************** skipping: [localhost] TASK [310: ASSIGN found_available_address TO TRUE] ********************************************************************************************* skipping: [localhost] PLAY RECAP ************************************************************************************************************************************* localhost : ok=11 changed=1 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0 You can see on the first run of TASK 310 that the task has been skipped because there was not an available address (I purposely assigned addresses to fill up the first IP prefix space). The second loop through there was an IP address assigned and created. Then the last iteration skipped since the IP address was already assigned. Looking in the Nautobot UI, you now see an IP address assigned by the automation.\nSummary #Combining the power of Nautobot as your source of truth about network data with the tools that the systems teams are using such as Ansible provides for a powerful combination. The Nautobot ecosystem is continuing to grow and the amount of capabilities enabled by Nautobot is tremendous. In an upcoming post, I will put together a Nautobot Job that will be able to complete the same activity and allow for an API call to be made by systems if you are not leveraging Ansible in your environment today.\nLet me know what you think on the social media at LinkedIn or Twitter.\n","date":"2023-06-05","permalink":"https://josh-v.com/nautobot-ip-provisioning/","section":"Posts","summary":"\u003cp\u003eOne of the great things about building an enterprise system, is being able to get systems to work cohesively amongst themselves to bring a complete solution. One of the workflows that is often required in a static IP address environment is the need to provide static IP addresses to hosts on a network segment. When using an IPAM (IP Address Management) solution such as Nautobot, the APIs and SDKs/modules made available for use in automation workflows is paramount to having the cohesion to make a seamless IT system.\u003c/p\u003e","title":"Nautobot IP Provisioning"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/graphql/","section":"Tags","summary":"","title":"Graphql"},{"content":"One of the features that I find myself using periodically that I think is underrated as far as using GraphQL is its ability to alias return keys in the response. This can be extremely helpful for developers writing applications, as it allows them to have the API response with the keys they are looking for. I have found this feature particularly useful when working on applications like Meraki and Nautobot together. In Nautobot a place is typically defined as the key site. In the Meraki world this is commonly set up as a network. Without GraphQL\u0026rsquo;s alias feature, the developer would need to translate this data over.\nLet\u0026rsquo;s explore two scenarios where a developer might choose to alias the response from GraphQL:\nQuick translation between systems Response from multiple queries I will demonstrate the capabilities of these scenarios using the Nautobot demo instance at https://demo.nautobot.com. For each of these, make sure that you have logged in already before going to the GraphiQL page.\nBase GraphQL Query #The base GraphQL query will be:\nquery { devices(name__ic: \u0026#34;den\u0026#34;) { name site { name } } } Note that I am filtering down to a smaller subset of the devices within Nautobot for brevity.\nWith the filter in place looking for devices that include bre in the name you get the response of:\n{ \u0026#34;data\u0026#34;: { \u0026#34;devices\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;den01-dist-01\u0026#34;, \u0026#34;site\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;DEN01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;den01-edge-01\u0026#34;, \u0026#34;site\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;DEN01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;den01-edge-02\u0026#34;, \u0026#34;site\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;DEN01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;den01-leaf-01\u0026#34;, \u0026#34;site\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;DEN01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;den01-leaf-02\u0026#34;, \u0026#34;site\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;DEN01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;den01-leaf-03\u0026#34;, \u0026#34;site\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;DEN01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;den01-leaf-04\u0026#34;, \u0026#34;site\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;DEN01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;den01-leaf-05\u0026#34;, \u0026#34;site\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;DEN01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;den01-leaf-06\u0026#34;, \u0026#34;site\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;DEN01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;den01-leaf-07\u0026#34;, \u0026#34;site\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;DEN01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;den01-leaf-08\u0026#34;, \u0026#34;site\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;DEN01\u0026#34; } } ] } } First of all, it\u0026rsquo;s pretty awesome to get just the data that you\u0026rsquo;re looking for. This is by far one of the best features of GraphQL. Use it whenever you are getting data from a system that offers GraphQL.\nGraphQL - Alias #Now with the request in place, let\u0026rsquo;s go down the path of changing the response where ever the key of site is found that GraphQL will instead send the key network. This will align more with the data format that Meraki is looking for within their API.\nThe alias is done by having instead of just site on line 4 of the query, but to add the new key name in front of a colon. Such as line 4 of the query will now be network: site {.\nquery { devices(name__ic: \u0026#34;bre\u0026#34;) { name network: site { name } } } And the response you notice you no longer see site: any where in the response.\n{ \u0026#34;data\u0026#34;: { \u0026#34;devices\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;bre01-dist-01\u0026#34;, \u0026#34;network\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;BRE01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;bre01-edge-01\u0026#34;, \u0026#34;network\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;BRE01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;bre01-edge-02\u0026#34;, \u0026#34;network\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;BRE01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;bre01-leaf-01\u0026#34;, \u0026#34;network\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;BRE01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;bre01-leaf-02\u0026#34;, \u0026#34;network\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;BRE01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;bre01-leaf-03\u0026#34;, \u0026#34;network\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;BRE01\u0026#34; } }, { \u0026#34;name\u0026#34;: \u0026#34;bre01-leaf-04\u0026#34;, \u0026#34;network\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;BRE01\u0026#34; } } ] } } GraphQL - Multiple Queries #A second case, which is from the GraphQL learning page is where you have multiple queries to the same part of the API. This is where you would then need to alias the response in order to make the GraphQL query to be valid.\nMultiple Search Query - Error #First let\u0026rsquo;s look at what a bad query looks like from GraphQL that will generate an error. Let\u0026rsquo;s say you want to get data about two sites from Nautobot. A query that would get you the data looks like:\nquery { sites(name: \u0026#34;ORD01\u0026#34;) { facility } sites(name: \u0026#34;DEN01\u0026#34;) { facility } } Running that query generates the following error message:\n\u0026#34;message\u0026#34;: \u0026#34;Fields \\\u0026#34;sites\\\u0026#34; conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.\u0026#34;, Multiple Search Query - Successful #The workaround is to alias the response. Such that instead of sites being sent back, we can use the site name with an incrementing counter number on the end. This may be something where you build a query offline in Python where you keep appending to a string, and in the end send over a large query with aliased keys.\nThe new query looks like this:\nquery { site1: sites(name: \u0026#34;ORD01\u0026#34;) { facility } site2: sites(name: \u0026#34;DEN01\u0026#34;) { facility } } There are now new site keys at the beginning. The response now gives you the data that you would expect:\n{ \u0026#34;data\u0026#34;: { \u0026#34;site1\u0026#34;: [ { \u0026#34;facility\u0026#34;: \u0026#34;O\u0026#39;Hare International Airport\u0026#34; } ], \u0026#34;site2\u0026#34;: [ { \u0026#34;facility\u0026#34;: \u0026#34;Denver International Airport\u0026#34; } ] } } Using in Python #Taking the first example to Python, let\u0026rsquo;s take a look at how you can then access the aliased response. The pynautobot package takes the response and has a convenience attribute of .json that will get the data into a Python dictionary for use. This will loop over each of the devices in the response and print the corresponding network (which will all be the same):\nimport json import click import pynautobot @click.command @click.option(\u0026#34;--nautobot_url\u0026#34;, envvar=\u0026#34;NAUTOBOT_URL\u0026#34;) @click.option(\u0026#34;--nautobot_token\u0026#34;, envvar=\u0026#34;NAUTOBOT_TOKEN\u0026#34;) def main(nautobot_url, nautobot_token): nautobot = pynautobot.api(url=nautobot_url, token=nautobot_token) query = \u0026#34;\u0026#34;\u0026#34; query { devices(name__ic: \u0026#34;den\u0026#34;) { name network: site { name } } } \u0026#34;\u0026#34;\u0026#34; graphql_response = nautobot.graphql.query(query=query) response = graphql_response.json for device in response[\u0026#34;data\u0026#34;][\u0026#34;devices\u0026#34;]: print(device[\u0026#34;network\u0026#34;]) if __name__ == \u0026#34;__main__\u0026#34;: main() Running that code with setting the appropriate environment variables of NAUTOBOT_URL and NAUTOBOT_TOKEN gets you this response:\n❯ python graphql_aliasing.py {\u0026#39;name\u0026#39;: \u0026#39;DEN01\u0026#39;} {\u0026#39;name\u0026#39;: \u0026#39;DEN01\u0026#39;} {\u0026#39;name\u0026#39;: \u0026#39;DEN01\u0026#39;} {\u0026#39;name\u0026#39;: \u0026#39;DEN01\u0026#39;} {\u0026#39;name\u0026#39;: \u0026#39;DEN01\u0026#39;} {\u0026#39;name\u0026#39;: \u0026#39;DEN01\u0026#39;} {\u0026#39;name\u0026#39;: \u0026#39;DEN01\u0026#39;} {\u0026#39;name\u0026#39;: \u0026#39;DEN01\u0026#39;} {\u0026#39;name\u0026#39;: \u0026#39;DEN01\u0026#39;} {\u0026#39;name\u0026#39;: \u0026#39;DEN01\u0026#39;} {\u0026#39;name\u0026#39;: \u0026#39;DEN01\u0026#39;} Summary #In summary, when you are able to use GraphQL to get data and the response may not be exactly the format you are looking for, take a look at using a GraphQL alias to get your response. This will come in super helpful over time. I\u0026rsquo;d love to hear what your thoughts are on this and where you are using it!\nHappy Automating!\n","date":"2022-12-17","permalink":"https://josh-v.com/graphql-aliasing/","section":"Posts","summary":"\u003cp\u003eOne of the features that I find myself using periodically that I think is underrated as far as using GraphQL is its ability to alias return keys in the response. This can be extremely helpful for developers writing applications, as it allows them to have the API response with the keys they are looking for. I have found this feature particularly useful when working on applications like Meraki and Nautobot together. In Nautobot a place is typically defined as the key \u003ccode\u003esite\u003c/code\u003e. In the Meraki world this is commonly set up as a \u003ccode\u003enetwork\u003c/code\u003e. Without GraphQL\u0026rsquo;s alias feature, the developer would need to translate this data over.\u003c/p\u003e","title":"GraphQL - Aliasing"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/meraki/","section":"Tags","summary":"","title":"Meraki"},{"content":"","date":null,"permalink":"https://josh-v.com/draft/","section":"Drafts","summary":"","title":"Drafts"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/nornir/","section":"Tags","summary":"","title":"Nornir"},{"content":"One of my favorite things to do as I get started with writing a piece of automation or code, is to lay out the design with pseudo code in the form of comments. I will do this with both Python and Ansible automations, and is a great way to get started on writing your automation or code. What pseudo code does for you is to layout the process at which you wish to accomplish a particular goal. And from there, you write the code that corresponds to the plain English wording of what is being done. By starting with pseudo code, you are starting with the process first. Then working on getting to the details of the code as you go.\nIn this post, I will take you through the writing of a Nautobot Job that is going to be used with the Nautobot Circuit Maintenance plugin, that will review the upcoming maintenance notifications that have been ingested into Nautobot and determining if there are any overlapping maintenances.\nThe Pseudo Code #My initial pseudo code for the Job looks like this:\n# Query for all of the circuits maintenances that are on going in the future # Query for all of the circuits within Nautobot # Loop over each of the circuit maintenance records # Check to see if there are any circuit maintenances that are duplicated time # Query to see how many circuits are available at the site # Determine how many other maintenances there may be at the same time # Report failures for any time where a circuit will take an outage # Evaluate adding a tag of an impeding site outage due to WAN outages (This is a design note, will probably be implemented) # Log success for each time there is a known circuit still available at the site at the same time Here I\u0026rsquo;m outlining with the general indentation of the planned code. Note that there is nothing about the actual code here. As I progress, I will be able to handle each of these independently. I will also try to keep the logic as minimal as I can. If I have to break out to a function because the logic is getting deep, this allows for the code to get tested quite well. This is the logic that I will put into an issue on GitHub to allow for the maintainers of the library to provide feedback on.\nThe testing subject will be a follow up post up after finishing up this post. I wish to keep things as short as possible on the topic.\nThe indented space starting on line 4 of the example are what will be executed under the for loop. So there is still some programming layout that is being done here, but does not have code.\nNow that there is a framework that is going to be worked from, the next steps are to start building the code.\nBuilding Out the Code #Now that the pseudo code is developed, it is putting code to the program (I so wanted to say something like pen to paper).\n","date":"2022-09-25","permalink":"https://josh-v.com/draft/python-pseudo-code/","section":"Drafts","summary":"\u003cp\u003eOne of my favorite things to do as I get started with writing a piece of automation or code, is to lay out the design with pseudo code in the form of comments. I will do this with both Python and Ansible automations, and is a great way to get started on writing your automation or code. What pseudo code does for you is to layout the process at which you wish to accomplish a particular goal. And from there, you write the code that corresponds to the plain English wording of what is being done. By starting with pseudo code, you are starting with the process first. Then working on getting to the details of the code as you go.\u003c/p\u003e","title":"Python Pseudo Code"},{"content":"This is a topic that I\u0026rsquo;m fairly opinionated on as of late is looking at what should be maintained within an inventory and the strategy of how to set up the inventory.\nFor the case of this blog post, I am going to use the term playbook to represent the automation being run. This is yes an Ansible term, but also apply this as your automation run that is using Nornir or any other automation framework.\nWhat Should Be In an Inventory #When taking a look at inventories there are usually a lot of options of what to include in your inventory for network devices. This can include the interfaces, VLANs on the device, BGP ASN, and connection information. For me, the only thing that should be in the inventory is connection information. All of the other items such as BGP ASN, Interface Names, and anything else should be on a playbook by playbook basis.\nAn inventory is meant to represent what could be automated. Because of this, extra information such as interface information should not be included. Your environment may have a lot of playbooks that the interface information is relevant, but it is not always the case. And because of this, that information should be gathered at runtime as the playbook executes, not as part of the inventory.\nThe only thing that should be in an inventory and is the basic needs of the inventory is connection information. This includes at a minimum the IP address/hostname of the device. It may also include SSH key/API key information or username and password to connect. This is also something that may be gathered as part of the process if you maintain this information inside of a password management system (such as Hashicorp Vault).\nInventory Strategy #When looking to set up your inventory, I would expect a minimal number of inventories. One that maintains production devices. One for development/test hosts. This may be broken up more into the teams that are responsible for particular devices, but if you can, I also argue that the devices across teams should be available within the same production inventory. This will allow for more automation collaboration to deliver results for the organization, which is the goal. There may be individual inventories for each of the scope of business, which may make sense as well, especially if there are some boundaries that may not want to be crossed.\nWhat about different sites? Well, these should all be within groups within the organization. If you have a large number of sites or buildings with a lot of gear, this is exactly where groups fit in. Both in Ansible and Nornir there is the concept of groups, which allows for the setup of the environment. There should not be an inventory per building/location, as this leads to difficulty as the automation scales.\nA Look At Inventories # Looking at the graphic above where there are 4 playbooks that you currently have. With an automation framework, your inventory should be the same inventory for each of the playbook activities. Whether it is for an OS version check, checking or configuring BGP neighbors, or configuring access interfaces. Of those examples, only the changing of the access interface playbook would need to know what the interfaces are. So to have the interfaces live in the inventory, especially if the inventory is gathered at run time, is causing unnecessary data gathering that may get in the way of automation.\nPossible Exception #One possible exception to this may be when using a system like AWX that maintains a database of inventory. In this setup, the inventory plugin execution may be run independently and at an off hours of automation usage. This would then allow for effective caching of these inventory data items. Since the inventory will then be used often without checking the source of truth for network devices, you would not be making unnecessary calls. This would still lead to ineffective loading of data though during development of playbooks. In development the inventory should be much lighter, which will allow for quick development regardless.\nHopefully this information will help to keep your automation environment up and running smoothly! Or if you are just getting started, a good place to start from!\nJosh\n","date":"2022-05-07","permalink":"https://josh-v.com/automation-inventory/","section":"Posts","summary":"\u003cp\u003eThis is a topic that I\u0026rsquo;m fairly opinionated on as of late is looking at what should be maintained within an inventory and the strategy of how to set up the inventory.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eFor the case of this blog post, I am going to use the term playbook to represent the automation being run. This is yes an Ansible term, but also apply this as your automation run that is using Nornir or any other automation framework.\u003c/p\u003e","title":"Automation Inventory"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/ccdevnetexpert/","section":"Tags","summary":"","title":"Ccdevnetexpert"},{"content":"This week Cisco announced the DevNet Expert certification exam. This exam and certification is something that I have been looking forward to for a long while. Dating back to the announcement of the certifications that were being provided. This was announced at Cisco Live 2019 in San Diego. I had started to lose some hope that this would become a reality with how long of a delay from the initial announcement to the announcement of availability. But it is now here. So here we go.\nDevNet Cert Requirements #For me this is going to be just the lab that is required. I already have the DevNet Professional certification, which has the DevCor exam as a requirement. This exam is the first requirement for the Expert certification. The second part is the lab, which is what the next several months of prep will be for me.\nHelpful Links #So far I have accumulated a few helpful links from the Internet and the various announcements. Here are the links:\nLab Topics Equipment and Software List DevNet Sandbox Let\u0026rsquo;s cover how I plan to use these and how I\u0026rsquo;m getting started with the studies, right away.\nLab Topics #This is my blueprint. I\u0026rsquo;m going to be working through each of these items to make sure that I have an adequate proficiency in this arena. The plan that I have is to make sure that I have the knowledge, skills, and abilities that are going to make for an efficient execution of the lab.\nEquipment and Software List #This is an excellent resource that Cisco is providing. I plan on having a VM built that is going to be emulating each of the pieces of the software on the device. I am anticipating that I will have a machine of similar capabilities, and maybe a UI to it in order to accomplish the practical lab.\nThird Party Software Requirements #The first thing that is standing out to me is that I am already pretty familiar with several of these tools that are listed. I have written about them in my Open Source Network Management book. So I initially feel very good about using these tools:\nTelegraf Grafana Nginx Docker Hashicorp Vault I also use Ansible on a regular occurrence, and same with GitLab. I have some exposure to InfluxDB, and this may be one of the greater learning curves compared to my general experience with Prometheus.\nDevNet Sandbox #The greatest thing that we have as individuals preparing for the exam is that we have a sandbox that provides access to some of the Cisco specific items that will need to be automated/worked on. With that in mind, I\u0026rsquo;ll be using that whenever possible. Hopefully the resources will be available for me as I look to leverage them.\nSummary #Overall I am definitely looking forward to taking a stab at the exam. I may have some other learnings that will come from this. The other aspect as I look at the exam topics is that it is in line with what I work with every day in the day job at Network to Code. This proficiency that is being tested matches up well with my day to day.\n","date":"2021-10-13","permalink":"https://josh-v.com/devnet-expert-starting-point/","section":"Posts","summary":"\u003cp\u003eThis week Cisco announced the DevNet Expert certification exam. This exam and certification is something that I have been looking forward to for a long while. Dating back to the announcement of the certifications that were being provided. This was announced at Cisco Live 2019 in San Diego. I had started to lose some hope that this would become a reality with how long of a delay from the initial announcement to the announcement of availability. But it is now here. So here we go.\u003c/p\u003e","title":"DevNet Expert - Starting Point"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/jobs/","section":"Tags","summary":"","title":"Jobs"},{"content":"One of the best features of Nautobot as a Network Automation Platform is the ability to create your own custom code. This is executed via a job. What makes Nautobot unique is its ability to integrate with a Git repository to get those jobs and code for use into Nautobot. This provides perhaps the simplest, authenticated, and logged methodology for building your own API endpoints.\nNautobot supplies an API endpoint to start execution of jobs. The big deal about why you would want to do this inside of Nautobot (even if you do not have any other data inside of Nautobot, but you should add data, it is a perk) is that you get an authentication mechanism with the Nautobot token setup and a logging mechanism. With Nautobot user accounts you can create tokens that will handle the API authentication. This is helpful that you do not need to add that into your own Flask, FastAPI, or Django application yourself. This is the same for the logging mechanism. Every job execution provides a log of the execution and the result.\nIn this post I\u0026rsquo;m going to walk you through adding an API endpoint using the Git synchronization capabilities of Nautobot. This will provide a mechanism to add users to a Meraki organization. This is meant as an example. You can definitely look to leverage this in your own Nautobot install as well.\nCreating Your Job #There are three options for creating Jobs within Nautobot.\nAdding a Python file to the Jobs Root directory Using a Git repository to sync the job to Nautobot Creating a Nautobot App (plugin) that leverages a Job Creating Your Git Repository #You can use any Git service that Nautobot has access to, and currently requires the repo to use a HTTPS endpoint. For this particular job setup, this will be hosted on GitHub. See the Nautobot docs for git as a data source for more details on setting up the Git sync. The following settings are used:\nNautobot Git Repo Setup\nFor the Git directory, the structure is going to look like the following:\n❯ tree . ├── jobs │ ├── __init__.py │ └── meraki_users.py └── README.md The jobs directory is required. This allows for the Git data source to have multiple data sources, for creating your job, this is required. The __init__.py file is also required for the jobs to be synced properly. The meraki_users.py file is a file that I have chosen to make. This is where the Job class will reside. This can be any file name that you wish. The README.md is not required for the jobs to work. This is good documentation.\nCreating the Nautobot Git Source #Navigate to Extensibility -\u0026gt; Git Repositories. Select + Add button on the upper right of the section. Fill in the information, with the required fields in bold. If user authentication is required for the Git repository (such as a private repo), then you need to use a Personal Access Token (PAT). Once you click Create a synchronization will occur that will sync the jobs from the Repo. Navigate to the Extensibility -\u0026gt; Jobs section. You will now see the jobs included on the page.\nFor the Meraki Users jobs that are being setup, I am using two environment variables to control the environment. MERAKI_DASHBOARD_API_KEY for the API key to use to talk to the Meraki Dashboard. NAUTOBOT_JOB_MERAKI_EMAIL_VALIDATION_REGEX which is the regex string to do validation on email within the form. This defaults to allowing all if not set.\nWriting the Code #Job Form and Vars #Once the git repository is setup, and you have seen what the interface to get the Nautobot environment to sync to the repository it is on to writing the code. You define your class that will inherit from the Job class. With that in place, the first thing you define at the class level is the variables to display on the form. There are many types of variable inputs that the Job form will display. This is just a few from the example provided:\nclass CreateUsers(Job): \u0026#34;\u0026#34;\u0026#34;Class to create a Meraki user Args: Job (Nautobot Job): Meraki create user job \u0026#34;\u0026#34;\u0026#34; user_email = StringVar( description=\u0026#34;User Email to add\u0026#34;, label=f\u0026#34;User Email, regex: {EMAIL_REGEX}\u0026#34;, required=True, regex=EMAIL_REGEX, ) meraki_access_level = ChoiceVar( description=\u0026#34;Level of access\u0026#34;, label=\u0026#34;Access Level\u0026#34;, choices=( (\u0026#34;full\u0026#34;, \u0026#34;Full\u0026#34;), (\u0026#34;read-only\u0026#34;, \u0026#34;Read Only\u0026#34;), (\u0026#34;enterprise\u0026#34;, \u0026#34;Enterprise\u0026#34;), (\u0026#34;none\u0026#34;, \u0026#34;None\u0026#34;), ), ) The user_email is a StringVar, that is a single line that will be presented. One of the options on the StringVar is the capability to complete a Regex validation. This is done with the regex key and provide it a regex to validate the response. This will provide the appropriate validation before launching the job. The meraki_access_level is a ChoiceVar that allows for multiple choices. This has a tuple type that is fed and provides for the value that is being provided as the first item in the tuple, and the display value on the second part of the tuple.\nThe various types of vars that can be displayed on a job form can be found within Nautobot\u0026rsquo;s code. The pre-defined Variable options are:\nBooleanVar ChoiceVar FileVar IntegerVar IPAddressVar IPAddressWithMaskVar IPNetworkVar MultiChoiceVar MultiObjectVar ObjectVar StringVar TextVar Job Meta data #The job section provides for a Meta class that will help to define data about the job class. This is where you can set a name, description, commit default setting, field order, and if this is read only or not.\nJob Code #Once the form has been defined and the meta data has been provided, you can add the run function. This is what gets executed. The first part of the Job execution, while not necessary, can be helpful is to assign the data coming in.\ndef __init__(self): super().__init__() self.data = None self.commit = None def run(self, data, commit): \u0026#34;\u0026#34;\u0026#34;Run execution Args: data (dict): Data from the form commit (bool): Commit changes to the database \u0026#34;\u0026#34;\u0026#34; self.data = data self.commit = commit if self.commit is False: self.log_info(obj=None, message=\u0026#34;Commit set to False\u0026#34;) self.log_info(obj=None, message=f\u0026#34;Data pushed in: {self.data}\u0026#34;) return Inside the class initialization the data and commit objects are created and assigned None. Inside the run, data and commit kwargs are passed in. The data key maintains the data from the form that was filled out. This is a dictionary that contains the data passed in. If using an ObjectVar type from Nautobot, the objects will be what come over. The commit key is a boolean true/false should the job be making updates to the Nautobot database.\nThis is the key part then in the run section. You can interact with other systems, such as in this case the Meraki Dashboard. As long as you can write Python to interact with the system, then you can have the Job execute the work. Of note here, those remote systems will have execution completed, even if commit is set to False. So you will need to put some logic into your class to handle commit to remote systems.\nAPI Details #API EndPoint #There is an API endpoint that is created when using Jobs to launch the jobs. This varies depending on the method that was used to load the Job into Nautobot. The base is https://nautobot.example.com/api/extras/jobs/. The next component of the API URL is the method, with using git, then there is also the data source slug, such as git.meraki-users. The slug is what is used on the right portion of the . in the section. Then you add the file name to the URL endpoint, and lastly the class name. The last section of the endpoint is /run/ knowing that this is the run endpoint.\nAPI Execution #The following execution, which was created with the Postman code snippet, shows what an execution would look like to launch a job. The two keys in the dictionary to pass to the endpoint are commit and data, just like what is passed into the class method. data\u0026rsquo;s value is a dictionary that has key/value pairs for each of the job fields, with the variables matching the Job UI variables.\nimport requests import json url = \u0026#34;https://nautobot.example.com/api/extras/jobs/git.meraki-jobs/meraki_users/CreateUsers/run/\u0026#34; payload = json.dumps({ \u0026#34;data\u0026#34;: { \u0026#34;user_email\u0026#34;: \u0026#34;josh@example.com\u0026#34;, \u0026#34;user_name\u0026#34;: \u0026#34;Josh Testing API\u0026#34;, \u0026#34;meraki_org_id\u0026#34;: \u0026#34;123456\u0026#34;, \u0026#34;meraki_network\u0026#34;: \u0026#34;MN01\u0026#34;, \u0026#34;meraki_access_level\u0026#34;: \u0026#34;full\u0026#34; }, \u0026#34;commit\u0026#34;: True }) headers = { \u0026#39;Authorization\u0026#39;: f\u0026#39;Token {os.getenv(\u0026#34;NAUTOBOT_TOKEN\u0026#34;)}\u0026#39;, \u0026#39;Content-Type\u0026#39;: \u0026#39;application/json\u0026#39;, } response = requests.request(\u0026#34;POST\u0026#34;, url, headers=headers, data=payload) print(response.text) API Response #The example response looks like:\n{ \u0026#34;url\u0026#34;: \u0026#34;https://nautobot.example.com/api/extras/jobs/git.meraki-jobs/meraki_users/CreateUsers/\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;git.meraki-jobs/meraki_users/CreateUsers\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Create Meraki User\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;test_methods\u0026#34;: [], \u0026#34;vars\u0026#34;: { \u0026#34;user_email\u0026#34;: \u0026#34;StringVar\u0026#34;, \u0026#34;user_name\u0026#34;: \u0026#34;StringVar\u0026#34;, \u0026#34;meraki_org_id\u0026#34;: \u0026#34;ChoiceVar\u0026#34;, \u0026#34;meraki_network\u0026#34;: \u0026#34;StringVar\u0026#34;, \u0026#34;meraki_access_level\u0026#34;: \u0026#34;ChoiceVar\u0026#34; }, \u0026#34;result\u0026#34;: { \u0026#34;id\u0026#34;: \u0026#34;d95051bb-4df6-4b45-a8a8-b967d4b3c66c\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://nautobot.example.com/api/extras/job-results/d95051bb-4df6-4b45-a8a8-b967d4b3c66c/\u0026#34;, \u0026#34;created\u0026#34;: \u0026#34;2021-10-09T12:34:42.024410-05:00\u0026#34;, \u0026#34;completed\u0026#34;: null, \u0026#34;name\u0026#34;: \u0026#34;git.meraki-jobs/meraki_users/CreateUsers\u0026#34;, \u0026#34;obj_type\u0026#34;: \u0026#34;extras.job\u0026#34;, \u0026#34;status\u0026#34;: { \u0026#34;value\u0026#34;: \u0026#34;pending\u0026#34;, \u0026#34;label\u0026#34;: \u0026#34;Pending\u0026#34; }, \u0026#34;user\u0026#34;: { \u0026#34;id\u0026#34;: \u0026#34;131e7ebb-3f50-48ad-8a64-4a0a077488de\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://nautobot.example.com/api/users/users/131e7ebb-3f50-48ad-8a64-a10a077488de/\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;test_user\u0026#34;, \u0026#34;display\u0026#34;: \u0026#34;test_user\u0026#34; }, \u0026#34;data\u0026#34;: null, \u0026#34;job_id\u0026#34;: \u0026#34;57a86756-175b-43c8-9b76-1df1340a46e7\u0026#34; } } The response gives an immediate response that the job status is pending within the result key. The biggest piece of data that is provided is the result[\u0026quot;url\u0026quot;] which you can use to get the status of the job. This job result will include the status, completed and start times, the total number of log statuses (success, warning, failure, info).\n{ \u0026#34;id\u0026#34;: \u0026#34;bf0405ef-4c1a-491e-a5bf-9d11cbdaa7ad\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://nautobot.example.com/api/extras/job-results/bf0405ef-4c1a-491e-a5bf-9d11cbdbb7ad/\u0026#34;, \u0026#34;created\u0026#34;: \u0026#34;2021-10-11T09:26:27.802264-05:00\u0026#34;, \u0026#34;completed\u0026#34;: \u0026#34;2021-10-11T09:26:30.602580-05:00\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;git.meraki-users/meraki_users/CreateUsers\u0026#34;, \u0026#34;obj_type\u0026#34;: \u0026#34;extras.job\u0026#34;, \u0026#34;status\u0026#34;: { \u0026#34;value\u0026#34;: \u0026#34;completed\u0026#34;, \u0026#34;label\u0026#34;: \u0026#34;Completed\u0026#34; }, \u0026#34;user\u0026#34;: { \u0026#34;id\u0026#34;: \u0026#34;131e7ebb-3f50-48ad-8a64-4a0a077488de\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://nautobot.example.com/api/users/users/131e7ebb-3f50-48ad-8a64-4a0a077488de/\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;test_user\u0026#34;, \u0026#34;display\u0026#34;: \u0026#34;test_user\u0026#34; }, \u0026#34;data\u0026#34;: { \u0026#34;run\u0026#34;: { \u0026#34;log\u0026#34;: [ [ \u0026#34;2021-10-11T14:26:30.591027+00:00\u0026#34;, \u0026#34;info\u0026#34;, null, null, \u0026#34;Commit set to False\u0026#34; ], [ \u0026#34;2021-10-11T14:26:30.591682+00:00\u0026#34;, \u0026#34;info\u0026#34;, null, null, \u0026#34;Data pushed in: {\u0026#39;user_email\u0026#39;: \u0026#39;josh@example.com\u0026#39;, \u0026#39;user_name\u0026#39;: \u0026#39;Josh Vanderaa\u0026#39;, \u0026#39;meraki_org_id\u0026#39;: \u0026#39;111111\u0026#39;, \u0026#39;meraki_network\u0026#39;: \u0026#39;\u0026#39;, \u0026#39;meraki_access_level\u0026#39;: \u0026#39;read-only\u0026#39;}\u0026#34; ], [ \u0026#34;2021-10-11T14:26:30.592706+00:00\u0026#34;, \u0026#34;info\u0026#34;, null, null, \u0026#34;Database changes have been reverted automatically.\u0026#34; ] ], \u0026#34;info\u0026#34;: 3, \u0026#34;failure\u0026#34;: 0, \u0026#34;success\u0026#34;: 0, \u0026#34;warning\u0026#34;: 0 }, \u0026#34;total\u0026#34;: { \u0026#34;info\u0026#34;: 3, \u0026#34;failure\u0026#34;: 0, \u0026#34;success\u0026#34;: 0, \u0026#34;warning\u0026#34;: 0 }, \u0026#34;output\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;job_id\u0026#34;: \u0026#34;0f9e369d-8cdd-4b52-a737-2af1e6c8bb92\u0026#34; } Summary #Nautobot is a tool that is well worth the time to look at. The capabilities that are being added to help Network Engineers manage, document, and automate the networks are well worth the investment. The Jobs end point and API endpoint are extremely helpful. And with having the capability to sync from a Git repository makes it easy to get started with an authenticated and logged API.\nFor more on getting started with several open source network management tools, including Nautobot in this post, take a look at my book on LeanPub - Open Source Network Management.\n","date":"2021-10-11","permalink":"https://josh-v.com/nautobot-jobs-execution/","section":"Posts","summary":"\u003cp\u003eOne of the best features of \u003ca href=\"https://nautobot.readthedocs.io/en/stable/\" target=\"_blank\" rel=\"noreferrer\"\u003eNautobot\u003c/a\u003e as a Network Automation Platform is the ability to create your own custom code. This is executed via a \u003ca href=\"https://nautobot.readthedocs.io/en/stable/additional-features/jobs/\" target=\"_blank\" rel=\"noreferrer\"\u003ejob\u003c/a\u003e. What makes Nautobot unique is its ability to integrate with a \u003ca href=\"https://nautobot.readthedocs.io/en/stable/models/extras/gitrepository/\" target=\"_blank\" rel=\"noreferrer\"\u003eGit repository\u003c/a\u003e to get those jobs and code for use into Nautobot. This provides perhaps the simplest, authenticated, and logged methodology for building your own API endpoints.\u003c/p\u003e\n\u003cp\u003eNautobot supplies an \u003ca href=\"https://nautobot.readthedocs.io/en/stable/additional-features/jobs/#via-the-api\" target=\"_blank\" rel=\"noreferrer\"\u003eAPI endpoint\u003c/a\u003e to start execution of jobs. The big deal about why you would want to do this inside of Nautobot (even if you do not have any other data inside of Nautobot, but you should add data, it is a perk) is that you get an authentication mechanism with the Nautobot token setup and a logging mechanism. With Nautobot user accounts you can create tokens that will handle the API authentication. This is helpful that you do not need to add that into your own Flask, FastAPI, or Django application yourself. This is the same for the logging mechanism. Every job execution provides a log of the execution and the result.\u003c/p\u003e","title":"Nautobot Jobs - Your Custom API Endpoint"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/networkmanagment/","section":"Tags","summary":"","title":"Networkmanagment"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/sourceoftruth/","section":"Tags","summary":"","title":"Sourceoftruth"},{"content":"Earlier this month I was able to hit the publish button on a new book - Open Source Network Management. The book dives into getting started with several open source network management tools. It is meant as a guide to help further your experience with using and installing open source tools, all on a single VM/host. The size of the host is meant to have minimal capital investment, in the way of a single NUC or a minimal VM deployed on a hypervisor in your environment.\nThe book is published on LeanPub, which is a publish early, publish often marketplace. The book is digital only, with PDF, ePub, and mobi formats available. Currently the book is indicating 80% completeness, with most of the technical content in place already! There are mainly soft edits in this early version.\nIt has been a while since I have put some content out on my blog site. Why? Well, this book is the reason why. The time that I would have been making some content here, I have been putting into making the book. This will change. I will be putting out a few more posts upcoming.\nTools At Launch #There are several open source tools that are covered. Starting out with installing Docker Community Edition (CE), then adding Docker Compose files to handle installation of the tools. After the Docker Compose is up, there is also a basic configuration to get up and running, actually using the project. Such as how to use the Nautobot Device Onboarding and Network Importer projects to get data into Nautobot. Or how to create a secrets vault to store your sensitive data, and then reference that data in other places. The current tool list includes:\nNautobot (Source of Truth) Hashicorp Vault (Secrets Management) Telegraf (Metrics Gathering) Prometheus (Metrics Storage and Alerting) Grafana (Metrics Visualization) NGINX (Web Server/Reverse Proxy) With these components in place a modern network management stack can be put into place, with minimal investment.\nTool Selection #These tools are all light weight tools that have the capability to be running on a single host to get up and running. Yet, after being light weight all will be able to scale out to meet the needs of some of the largest networks.\nThank You #Hopefully the content in the book is helpful! It was an enjoyable time to put it together.\n-Josh\n","date":"2021-09-14","permalink":"https://josh-v.com/book-open-source-network-management/","section":"Posts","summary":"\u003cp\u003eEarlier this month I was able to hit the \u003cem\u003epublish\u003c/em\u003e button on a new book - Open Source Network Management. The book dives into getting started with several open source network management tools. It is meant as a guide to help further your experience with using and installing open source tools, all on a single VM/host. The size of the host is meant to have minimal capital investment, in the way of a single NUC or a minimal VM deployed on a hypervisor in your environment.\u003c/p\u003e","title":"New Book - Open Source Network Management"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/opensource/","section":"Tags","summary":"","title":"Opensource"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/arista/","section":"Tags","summary":"","title":"Arista"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/juniper/","section":"Tags","summary":"","title":"Juniper"},{"content":"All of the work through the modules thus far in the series have brought us to what we all want to see. How to get or update device information inside of Nautobot. Adding of sites, device types, device roles are required to get us to this point. Now you can see how to add a device to Nautobot using the networktocode.nautobot.device module.\nThere are many optional parameters for the module specifically. I encourage you to take a look at the module documentation (linked below) in order to get a good sense of all of the options available. The required parameters for a device that is present are:\ndevice_role device_type name site status An important caveat for me is that this is something that should be done with rarity. Only when truly adding a device to Nautobot, in a programmatic way this should be used. I do not advocate for running this module constantly based on your devices. The idea is to get Nautobot to be your source of truth about devices, not to have devices be the source of truth and updating Nautobot.\nSo where do I see this being run? I do absolutely see it being a part of a pipeline or a service portal. The idea being that the service portal has a request for a new site to be turned up. That in turn kicks off an Ansible Playbook that will make the necessary updates to Nautobot, and is done in a consistent manor.\nModule Documentation # Read the Docs GitHub This module does require pynautobot to execute properly\nEnvironment #For this demo, here are the versions shown:\nComponent Version Nautobot v1.0.0b2 Nautobot Ansible Collection v1.0.3 pynautobot 1.0.1 Data File #The Nautobot devices file is going to be a little bit more involved. In this particular demo case there are no existing inventories to use. If you want to see a demo of a similar how to add devices to NetBox using an existing Ansible inventory, I encourage you to take a look at my GitHub repository where I did a Meetup video on working with Ansible + NetBox. The same/similar concept can be used with Nautobot.\nTo simulate the idea that we are going to be running a playbook execution as part of a service request, here is the data file that will be fed to the Ansible playbook:\n# group_vars/all/devices.yml --- devices: - name: \u0026#34;grb-rtr01\u0026#34; site: \u0026#34;GRB\u0026#34; device_role: \u0026#34;Router\u0026#34; device_type: \u0026#34;IOSv\u0026#34; - name: \u0026#34;msp-rtr01\u0026#34; site: \u0026#34;MSP\u0026#34; device_role: \u0026#34;Router\u0026#34; device_type: \u0026#34;IOSv\u0026#34; Example #Example - Adding Device Types #First if you are following along with the examples thus far, I made a new site here. So in order to accommodate the new site, I added GRB and re-ran the playbook to create sites. That was done successfully and idempotently with only the GRB site being added.\n--- - name: \u0026#34;ADD DEVICES\u0026#34; hosts: localhost connection: local gather_facts: false # No gathering facts about the container execution env tasks: - name: \u0026#34;05 - ADD DEVICES\u0026#34; networktocode.nautobot.device: url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_URL\u0026#39;) }}\u0026#34; token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_TOKEN\u0026#39;) }}\u0026#34; data: name: \u0026#34;{{ item[\u0026#39;name\u0026#39;] }}\u0026#34; site: \u0026#34;{{ item[\u0026#39;site\u0026#39;] }}\u0026#34; device_role: \u0026#34;{{ item[\u0026#39;device_role\u0026#39;] }}\u0026#34; device_type: \u0026#34;{{ item[\u0026#39;device_type\u0026#39;] }}\u0026#34; platform: \u0026#34;IOS\u0026#34; status: \u0026#34;Active\u0026#34; # Newly required for Nautobot, a status of some kind loop: \u0026#34;{{ devices }}\u0026#34; Example - Execution #Before the execution there are no devices within Nautobot:\nThis execution shows that all of the device types are added.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 josh-v@1297da6292df:~$ ansible-playbook add_devices.yml -vv ansible-playbook 2.10.6 config file = /local/ansible.cfg configured module search path = [\u0026#39;/local/.ansible/plugins/modules\u0026#39;, \u0026#39;/usr/share/ansible/plugins/modules\u0026#39;] ansible python module location = /usr/local/lib/python3.7/site-packages/ansible executable location = /usr/local/bin/ansible-playbook python version = 3.7.10 (default, Feb 16 2021, 19:28:34) [GCC 8.3.0] Using /local/ansible.cfg as config file [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; redirecting (type: callback) ansible.builtin.yaml to community.general.yaml redirecting (type: callback) ansible.builtin.yaml to community.general.yaml Skipping callback \u0026#39;default\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;minimal\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;oneline\u0026#39;, as we already have a stdout callback. PLAYBOOK: add_devices.yml ******************************************************************************************************* 1 plays in add_devices.yml PLAY [ADD DEVICES] ************************************************************************************************************** META: ran handlers TASK [05 - ADD DEVICES] ********************************************************************************************************* task path: /local/add_devices.yml:7 changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;grb-rtr01\u0026#39;, \u0026#39;site\u0026#39;: \u0026#39;GRB\u0026#39;, \u0026#39;device_role\u0026#39;: \u0026#39;Router\u0026#39;, \u0026#39;device_type\u0026#39;: \u0026#39;IOSv\u0026#39;}) =\u0026gt; changed=true ansible_loop_var: item device: asset_tag: null cluster: null comments: \u0026#39;\u0026#39; config_context: {} created: \u0026#39;2021-03-28\u0026#39; custom_fields: {} device_role: c6909cfd-0fd9-4ab1-b0e7-58493fff84b7 device_type: 70504d2c-1641-4e6d-be40-192eb1d6e0c0 display_name: grb-rtr01 face: null id: 7757ffbd-cca8-49ee-978f-66fbdb3f6b14 last_updated: \u0026#39;2021-03-28T15:48:29.324146Z\u0026#39; local_context_data: null name: grb-rtr01 parent_device: null platform: 96d58cc1-ad7e-43c7-a79f-db748e6eb894 position: null primary_ip: null primary_ip4: null primary_ip6: null rack: null serial: \u0026#39;\u0026#39; site: c92f368a-93a0-472b-a391-b9e9665b42a4 status: active tags: [] tenant: null url: http://nautobot-demo.josh-v.com/api/dcim/devices/7757ffbd-cca8-49ee-978f-66fbdb3f6b14/ vc_position: null vc_priority: null virtual_chassis: null item: device_role: Router device_type: IOSv name: grb-rtr01 site: GRB msg: device grb-rtr01 created changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;msp-rtr01\u0026#39;, \u0026#39;site\u0026#39;: \u0026#39;MSP\u0026#39;, \u0026#39;device_role\u0026#39;: \u0026#39;Router\u0026#39;, \u0026#39;device_type\u0026#39;: \u0026#39;IOSv\u0026#39;}) =\u0026gt; changed=true ansible_loop_var: item device: asset_tag: null cluster: null comments: \u0026#39;\u0026#39; config_context: {} created: \u0026#39;2021-03-28\u0026#39; custom_fields: {} device_role: c6909cfd-0fd9-4ab1-b0e7-58493fff84b7 device_type: 70504d2c-1641-4e6d-be40-192eb1d6e0c0 display_name: msp-rtr01 face: null id: 72f14290-865d-43a6-af7b-4e29c6400460 last_updated: \u0026#39;2021-03-28T15:48:30.966452Z\u0026#39; local_context_data: null name: msp-rtr01 parent_device: null platform: 96d58cc1-ad7e-43c7-a79f-db748e6eb894 position: null primary_ip: null primary_ip4: null primary_ip6: null rack: null serial: \u0026#39;\u0026#39; site: c72cce62-1dd6-483e-90a3-3331ea3155a8 status: active tags: [] tenant: null url: http://nautobot-demo.josh-v.com/api/dcim/devices/72f14290-865d-43a6-af7b-4e29c6400460/ vc_position: null vc_priority: null virtual_chassis: null item: device_role: Router device_type: IOSv name: msp-rtr01 site: MSP msg: device msp-rtr01 created META: ran handlers META: ran handlers PLAY RECAP ********************************************************************************************************************** localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 The second execution of playbook shows that with these three settings the module is idempotent:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 josh-v@1297da6292df:~$ ansible-playbook add_devices.yml -vv ansible-playbook 2.10.6 config file = /local/ansible.cfg configured module search path = [\u0026#39;/local/.ansible/plugins/modules\u0026#39;, \u0026#39;/usr/share/ansible/plugins/modules\u0026#39;] ansible python module location = /usr/local/lib/python3.7/site-packages/ansible executable location = /usr/local/bin/ansible-playbook python version = 3.7.10 (default, Feb 16 2021, 19:28:34) [GCC 8.3.0] Using /local/ansible.cfg as config file [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; redirecting (type: callback) ansible.builtin.yaml to community.general.yaml redirecting (type: callback) ansible.builtin.yaml to community.general.yaml Skipping callback \u0026#39;default\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;minimal\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;oneline\u0026#39;, as we already have a stdout callback. PLAYBOOK: add_devices.yml ******************************************************************************************************* 1 plays in add_devices.yml PLAY [ADD DEVICES] ************************************************************************************************************** META: ran handlers TASK [05 - ADD DEVICES] ********************************************************************************************************* task path: /local/add_devices.yml:7 ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;grb-rtr01\u0026#39;, \u0026#39;site\u0026#39;: \u0026#39;GRB\u0026#39;, \u0026#39;device_role\u0026#39;: \u0026#39;Router\u0026#39;, \u0026#39;device_type\u0026#39;: \u0026#39;IOSv\u0026#39;}) =\u0026gt; changed=false ansible_loop_var: item device: asset_tag: null cluster: null comments: \u0026#39;\u0026#39; config_context: {} created: \u0026#39;2021-03-28\u0026#39; custom_fields: {} device_role: c6909cfd-0fd9-4ab1-b0e7-58493fff84b7 device_type: 70504d2c-1641-4e6d-be40-192eb1d6e0c0 display_name: grb-rtr01 face: null id: 7757ffbd-cca8-49ee-978f-66fbdb3f6b14 last_updated: \u0026#39;2021-03-28T15:48:29.324146Z\u0026#39; local_context_data: null name: grb-rtr01 parent_device: null platform: 96d58cc1-ad7e-43c7-a79f-db748e6eb894 position: null primary_ip: null primary_ip4: null primary_ip6: null rack: null serial: \u0026#39;\u0026#39; site: c92f368a-93a0-472b-a391-b9e9665b42a4 status: active tags: [] tenant: null url: http://nautobot-demo.josh-v.com/api/dcim/devices/7757ffbd-cca8-49ee-978f-66fbdb3f6b14/ vc_position: null vc_priority: null virtual_chassis: null item: device_role: Router device_type: IOSv name: grb-rtr01 site: GRB msg: device grb-rtr01 already exists ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;msp-rtr01\u0026#39;, \u0026#39;site\u0026#39;: \u0026#39;MSP\u0026#39;, \u0026#39;device_role\u0026#39;: \u0026#39;Router\u0026#39;, \u0026#39;device_type\u0026#39;: \u0026#39;IOSv\u0026#39;}) =\u0026gt; changed=false ansible_loop_var: item device: asset_tag: null cluster: null comments: \u0026#39;\u0026#39; config_context: {} created: \u0026#39;2021-03-28\u0026#39; custom_fields: {} device_role: c6909cfd-0fd9-4ab1-b0e7-58493fff84b7 device_type: 70504d2c-1641-4e6d-be40-192eb1d6e0c0 display_name: msp-rtr01 face: null id: 72f14290-865d-43a6-af7b-4e29c6400460 last_updated: \u0026#39;2021-03-28T15:48:30.966452Z\u0026#39; local_context_data: null name: msp-rtr01 parent_device: null platform: 96d58cc1-ad7e-43c7-a79f-db748e6eb894 position: null primary_ip: null primary_ip4: null primary_ip6: null rack: null serial: \u0026#39;\u0026#39; site: c72cce62-1dd6-483e-90a3-3331ea3155a8 status: active tags: [] tenant: null url: http://nautobot-demo.josh-v.com/api/dcim/devices/72f14290-865d-43a6-af7b-4e29c6400460/ vc_position: null vc_priority: null virtual_chassis: null item: device_role: Router device_type: IOSv name: msp-rtr01 site: MSP msg: device msp-rtr01 already exists META: ran handlers META: ran handlers PLAY RECAP ********************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Post Execution #After the execution and notice that the module is idempotent, the two devices shown are all set to be added.\nSummary #Now that you have some devices, you can start to do a little bit more with your Nautobot environment. This playbook purposely did not add more information about the device yet, such as the serial number, interfaces, or IP addressing. This is all information that you can add more about the device as well using Ansible Facts and Resource Modules to continue to develop your source of truth. More likely to come in the future, or you can check out the content on GitHub and YouTube referenced above for immediate reference.\nGetting devices into Nautobot provides a powerful place to put your source of truth for automation. It does take a small bit to get to a good place, but with a little bit of effort up front you can get things done in a consistent and repeatable fashion. No more having to do things by hand with the data points.\nHope this has helped. If so, let me know with a comment below or give a thumbs up on the post.\n","date":"2021-03-28","permalink":"https://josh-v.com/nautobot-ansible-devices/","section":"Posts","summary":"\u003cp\u003eAll of the work through the modules thus far in the series have brought us to what we all want to see. How to get or update device information inside of Nautobot. Adding of sites, device types, device roles are required to get us to this point. Now you can see how to add a device to Nautobot using the networktocode.nautobot.device module.\u003c/p\u003e\n\u003cp\u003eThere are many optional parameters for the module specifically. I encourage you to take a look at the module documentation (linked below) in order to get a good sense of all of the options available. The required parameters for a device that is present are:\u003c/p\u003e","title":"Nautobot Ansible Collection: Devices"},{"content":"A device type is the next piece in the Nautobot Device onboarding requirements. The device type corresponds to the model number of the hardware (or virtual machine). This is where you are able to template out devices during their creation. So if you have a console port on a device type, that console port will be created when you create the device. However, there is NOT a relationship built between the device type and the device. If the device type gets updated after the device is created, the device itself is not updated.\nModule Documentation # Read the Docs GitHub This module does require pynautobot to execute properly\nEnvironment #For this demo, here are the versions shown:\nComponent Version Nautobot v1.0.0b2 Nautobot Ansible Collection v1.0.3 pynautobot 1.0.1 Data File #This gets to be a little more of the complex data source types. There are many data parameters that are good to include. The minimum data parameter has just the model. But there are going to be many more options as you build out your Nautobot environment that feeds into the data correlation that makes Nautobot a pleasure to use. Such as the manufacturer that it is tied to, the part number, and the u_height as you build rack diagrams from Nautobot.\nIn the demo the model, manufacturer, part number, and slug will get defined. The slug will be the lower case of the model name. The primary key is the model name in this case.\n--- device_types: - model: \u0026#34;ASAv\u0026#34; manufacturer: \u0026#34;Cisco\u0026#34; slug: \u0026#34;asav\u0026#34; part_number: \u0026#34;asav\u0026#34; - model: \u0026#34;CSR1000v\u0026#34; manufacturer: \u0026#34;Cisco\u0026#34; slug: \u0026#34;csr1000v\u0026#34; part_number: \u0026#34;csr1000v\u0026#34; - model: \u0026#34;IOSv\u0026#34; manufacturer: \u0026#34;Cisco\u0026#34; slug: \u0026#34;iosv\u0026#34; part_number: \u0026#34;iosv\u0026#34; - model: \u0026#34;nxosv\u0026#34; manufacturer: \u0026#34;Cisco\u0026#34; slug: \u0026#34;nxosv\u0026#34; part_number: \u0026#34;nxosv\u0026#34; - model: \u0026#34;vEOS\u0026#34; manufacturer: \u0026#34;Arista\u0026#34; slug: \u0026#34;veos\u0026#34; part_number: \u0026#34;veos\u0026#34; Example #Example - Adding Device Types #Getting started I already have a Cisco manufacturer included from a different demo. This will not hurt what is being demonstrated here. The task to add a manufacturer looks like:\n--- - name: \u0026#34;ADD DEVICE TYPES\u0026#34; hosts: localhost connection: local gather_facts: false # No gathering facts about the container execution env tasks: - name: \u0026#34;05 - ADD DEVICE TYPES\u0026#34; networktocode.nautobot.device_type: url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_URL\u0026#39;) }}\u0026#34; token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_TOKEN\u0026#39;) }}\u0026#34; data: model: \u0026#34;{{ item[\u0026#39;model\u0026#39;] }}\u0026#34; manufacturer: \u0026#34;{{ item[\u0026#39;manufacturer\u0026#39;] }}\u0026#34; slug: \u0026#34;{{ item[\u0026#39;slug\u0026#39;] }}\u0026#34; part_number: \u0026#34;{{ item[\u0026#39;part_number\u0026#39;] }}\u0026#34; loop: \u0026#34;{{ device_types }}\u0026#34; Example - Execution #This execution shows that all of the device types are added. Before the execution Nautobot does not have any device types.\njosh-v@60a6498959f8:~$ ansible-playbook add_device_types.yml -vv ansible-playbook 2.10.6 config file = /local/ansible.cfg configured module search path = [\u0026#39;/local/.ansible/plugins/modules\u0026#39;, \u0026#39;/usr/share/ansible/plugins/modules\u0026#39;] ansible python module location = /usr/local/lib/python3.7/site-packages/ansible executable location = /usr/local/bin/ansible-playbook python version = 3.7.10 (default, Feb 16 2021, 19:28:34) [GCC 8.3.0] Using /local/ansible.cfg as config file [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; redirecting (type: callback) ansible.builtin.yaml to community.general.yaml redirecting (type: callback) ansible.builtin.yaml to community.general.yaml Skipping callback \u0026#39;default\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;minimal\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;oneline\u0026#39;, as we already have a stdout callback. PLAYBOOK: add_device_types.yml ************************************************************************************************************************** 1 plays in add_device_types.yml PLAY [ADD DEVICE TYPES] ********************************************************************************************************************************* META: ran handlers TASK [05 - ADD DEVICE TYPES] **************************************************************************************************************************** task path: /local/add_device_types.yml:7 changed: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;ASAv\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;asav\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;asav\u0026#39;}) =\u0026gt; changed=true ansible_loop_var: item device_type: comments: \u0026#39;\u0026#39; created: \u0026#39;2021-03-16\u0026#39; custom_fields: {} display_name: Cisco ASAv front_image: null id: 0bdb5944-a2c2-4093-a83c-c8c69485d5ac is_full_depth: true last_updated: \u0026#39;2021-03-16T00:15:49.347705Z\u0026#39; manufacturer: 58c56ff8-f507-4356-9b6f-915be289831b model: ASAv part_number: asav rear_image: null slug: asav subdevice_role: null tags: [] u_height: 1 url: http://nautobot-demo.josh-v.com/api/dcim/device-types/0bdb5944-a2c2-4093-a83c-c8c69485d5ac/ item: manufacturer: Cisco model: ASAv part_number: asav slug: asav msg: device_type asav created changed: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;CSR1000v\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;csr1000v\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;csr1000v\u0026#39;}) =\u0026gt; changed=true ansible_loop_var: item device_type: comments: \u0026#39;\u0026#39; created: \u0026#39;2021-03-16\u0026#39; custom_fields: {} display_name: Cisco CSR1000v front_image: null id: a804d796-194a-46e2-af72-bdfbc179af92 is_full_depth: true last_updated: \u0026#39;2021-03-16T00:15:50.335484Z\u0026#39; manufacturer: 58c56ff8-f507-4356-9b6f-915be289831b model: CSR1000v part_number: csr1000v rear_image: null slug: csr1000v subdevice_role: null tags: [] u_height: 1 url: http://nautobot-demo.josh-v.com/api/dcim/device-types/a804d796-194a-46e2-af72-bdfbc179af92/ item: manufacturer: Cisco model: CSR1000v part_number: csr1000v slug: csr1000v msg: device_type csr1000v created changed: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;IOSv\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;iosv\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;iosv\u0026#39;}) =\u0026gt; changed=true ansible_loop_var: item device_type: comments: \u0026#39;\u0026#39; created: \u0026#39;2021-03-16\u0026#39; custom_fields: {} display_name: Cisco IOSv front_image: null id: 70504d2c-1641-4e6d-be40-192eb1d6e0c0 is_full_depth: true last_updated: \u0026#39;2021-03-16T00:15:51.095938Z\u0026#39; manufacturer: 58c56ff8-f507-4356-9b6f-915be289831b model: IOSv part_number: iosv rear_image: null slug: iosv subdevice_role: null tags: [] u_height: 1 url: http://nautobot-demo.josh-v.com/api/dcim/device-types/70504d2c-1641-4e6d-be40-192eb1d6e0c0/ item: manufacturer: Cisco model: IOSv part_number: iosv slug: iosv msg: device_type iosv created changed: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;nxosv\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;nxosv\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;nxosv\u0026#39;}) =\u0026gt; changed=true ansible_loop_var: item device_type: comments: \u0026#39;\u0026#39; created: \u0026#39;2021-03-16\u0026#39; custom_fields: {} display_name: Cisco nxosv front_image: null id: 177ec0e0-6455-4d0c-9074-ee3f519cdcd8 is_full_depth: true last_updated: \u0026#39;2021-03-16T00:15:51.913297Z\u0026#39; manufacturer: 58c56ff8-f507-4356-9b6f-915be289831b model: nxosv part_number: nxosv rear_image: null slug: nxosv subdevice_role: null tags: [] u_height: 1 url: http://nautobot-demo.josh-v.com/api/dcim/device-types/177ec0e0-6455-4d0c-9074-ee3f519cdcd8/ item: manufacturer: Cisco model: nxosv part_number: nxosv slug: nxosv msg: device_type nxosv created changed: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;vEOS\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Arista\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;veos\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;veos\u0026#39;}) =\u0026gt; changed=true ansible_loop_var: item device_type: comments: \u0026#39;\u0026#39; created: \u0026#39;2021-03-16\u0026#39; custom_fields: {} display_name: Arista vEOS front_image: null id: f93a4194-a71d-4532-b6f7-0af6e67f8155 is_full_depth: true last_updated: \u0026#39;2021-03-16T00:15:52.673428Z\u0026#39; manufacturer: fdead7a3-58a6-4a62-bb52-e21bbe2c6bf7 model: vEOS part_number: veos rear_image: null slug: veos subdevice_role: null tags: [] u_height: 1 url: http://nautobot-demo.josh-v.com/api/dcim/device-types/f93a4194-a71d-4532-b6f7-0af6e67f8155/ item: manufacturer: Arista model: vEOS part_number: veos slug: veos msg: device_type veos created META: ran handlers META: ran handlers PLAY RECAP ********************************************************************************************************************************************** localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 At this point the device types are now available inside of the UI.\nThe second execution of playbook shows that with these three settings the module is idempotent:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 josh-v@60a6498959f8:~$ ansible-playbook add_device_types.yml -vv ansible-playbook 2.10.6 config file = /local/ansible.cfg configured module search path = [\u0026#39;/local/.ansible/plugins/modules\u0026#39;, \u0026#39;/usr/share/ansible/plugins/modules\u0026#39;] ansible python module location = /usr/local/lib/python3.7/site-packages/ansible executable location = /usr/local/bin/ansible-playbook python version = 3.7.10 (default, Feb 16 2021, 19:28:34) [GCC 8.3.0] Using /local/ansible.cfg as config file [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; redirecting (type: callback) ansible.builtin.yaml to community.general.yaml redirecting (type: callback) ansible.builtin.yaml to community.general.yaml Skipping callback \u0026#39;default\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;minimal\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;oneline\u0026#39;, as we already have a stdout callback. PLAYBOOK: add_device_types.yml ************************************************************************************************************************** 1 plays in add_device_types.yml PLAY [ADD DEVICE TYPES] ********************************************************************************************************************************* META: ran handlers TASK [05 - ADD DEVICE TYPES] **************************************************************************************************************************** task path: /local/add_device_types.yml:7 ok: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;ASAv\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;asav\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;asav\u0026#39;}) =\u0026gt; changed=false ansible_loop_var: item device_type: comments: \u0026#39;\u0026#39; created: \u0026#39;2021-03-16\u0026#39; custom_fields: {} device_count: 0 display_name: Cisco ASAv front_image: null id: 0bdb5944-a2c2-4093-a83c-c8c69485d5ac is_full_depth: true last_updated: \u0026#39;2021-03-16T00:15:49.347705Z\u0026#39; manufacturer: 58c56ff8-f507-4356-9b6f-915be289831b model: ASAv part_number: asav rear_image: null slug: asav subdevice_role: null tags: [] u_height: 1 url: http://nautobot-demo.josh-v.com/api/dcim/device-types/0bdb5944-a2c2-4093-a83c-c8c69485d5ac/ item: manufacturer: Cisco model: ASAv part_number: asav slug: asav msg: device_type asav already exists ok: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;CSR1000v\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;csr1000v\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;csr1000v\u0026#39;}) =\u0026gt; changed=false ansible_loop_var: item device_type: comments: \u0026#39;\u0026#39; created: \u0026#39;2021-03-16\u0026#39; custom_fields: {} device_count: 0 display_name: Cisco CSR1000v front_image: null id: a804d796-194a-46e2-af72-bdfbc179af92 is_full_depth: true last_updated: \u0026#39;2021-03-16T00:15:50.335484Z\u0026#39; manufacturer: 58c56ff8-f507-4356-9b6f-915be289831b model: CSR1000v part_number: csr1000v rear_image: null slug: csr1000v subdevice_role: null tags: [] u_height: 1 url: http://nautobot-demo.josh-v.com/api/dcim/device-types/a804d796-194a-46e2-af72-bdfbc179af92/ item: manufacturer: Cisco model: CSR1000v part_number: csr1000v slug: csr1000v msg: device_type csr1000v already exists ok: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;IOSv\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;iosv\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;iosv\u0026#39;}) =\u0026gt; changed=false ansible_loop_var: item device_type: comments: \u0026#39;\u0026#39; created: \u0026#39;2021-03-16\u0026#39; custom_fields: {} device_count: 0 display_name: Cisco IOSv front_image: null id: 70504d2c-1641-4e6d-be40-192eb1d6e0c0 is_full_depth: true last_updated: \u0026#39;2021-03-16T00:15:51.095938Z\u0026#39; manufacturer: 58c56ff8-f507-4356-9b6f-915be289831b model: IOSv part_number: iosv rear_image: null slug: iosv subdevice_role: null tags: [] u_height: 1 url: http://nautobot-demo.josh-v.com/api/dcim/device-types/70504d2c-1641-4e6d-be40-192eb1d6e0c0/ item: manufacturer: Cisco model: IOSv part_number: iosv slug: iosv msg: device_type iosv already exists ok: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;nxosv\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;nxosv\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;nxosv\u0026#39;}) =\u0026gt; changed=false ansible_loop_var: item device_type: comments: \u0026#39;\u0026#39; created: \u0026#39;2021-03-16\u0026#39; custom_fields: {} device_count: 0 display_name: Cisco nxosv front_image: null id: 177ec0e0-6455-4d0c-9074-ee3f519cdcd8 is_full_depth: true last_updated: \u0026#39;2021-03-16T00:15:51.913297Z\u0026#39; manufacturer: 58c56ff8-f507-4356-9b6f-915be289831b model: nxosv part_number: nxosv rear_image: null slug: nxosv subdevice_role: null tags: [] u_height: 1 url: http://nautobot-demo.josh-v.com/api/dcim/device-types/177ec0e0-6455-4d0c-9074-ee3f519cdcd8/ item: manufacturer: Cisco model: nxosv part_number: nxosv slug: nxosv msg: device_type nxosv already exists ok: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;vEOS\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Arista\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;veos\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;veos\u0026#39;}) =\u0026gt; changed=false ansible_loop_var: item device_type: comments: \u0026#39;\u0026#39; created: \u0026#39;2021-03-16\u0026#39; custom_fields: {} device_count: 0 display_name: Arista vEOS front_image: null id: f93a4194-a71d-4532-b6f7-0af6e67f8155 is_full_depth: true last_updated: \u0026#39;2021-03-16T00:15:52.673428Z\u0026#39; manufacturer: fdead7a3-58a6-4a62-bb52-e21bbe2c6bf7 model: vEOS part_number: veos rear_image: null slug: veos subdevice_role: null tags: [] u_height: 1 url: http://nautobot-demo.josh-v.com/api/dcim/device-types/f93a4194-a71d-4532-b6f7-0af6e67f8155/ item: manufacturer: Arista model: vEOS part_number: veos slug: veos msg: device_type veos already exists META: ran handlers META: ran handlers PLAY RECAP ********************************************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 After completion of this you will have the device types (hardware models) available for you to assign to devices (coming up next).\nSummary #Device types are important so you know what model of devices you have to work with. This will come in handy as well in your automations that if you have a particular device type that you need to do something against. Such as having a separate type for Cisco Catalyst 3750G vs Catalyst 3750X. They are all 3750 switches, however you may need to apply a unique configuration set against a particular device type. By having this predefined in your source of truth, you are all set to be able to run automations against each.\nHope this has helped. If so, let me know with a comment below or give a thumbs up on the post. Feel free to connect with me on Twitter @vanderaaj\n","date":"2021-03-15","permalink":"https://josh-v.com/nautobot-ansible-device-types/","section":"Posts","summary":"\u003cp\u003eA device type is the next piece in the Nautobot Device onboarding requirements. The device type corresponds to the model number of the hardware (or virtual machine). This is where you are able to template out devices during their creation. So if you have a console port on a device type, that console port will be created when you create the device. However, \u003cstrong\u003ethere is NOT\u003c/strong\u003e a relationship built between the device type and the device. If the device type gets updated after the device is created, the device itself is \u003cstrong\u003enot\u003c/strong\u003e updated.\u003c/p\u003e","title":"Nautobot Ansible Collection: Device Types"},{"content":"A device role is aptly named, the role of the device. This is likely to be something that is meaningful to your organization and could change. For example you may have the 3 tier system of Core, Distribution, and Access layer environments. These are just fine. So you would want to have the roles there to reflect this reality. You may have leaf-spine environments, there are two more roles. And in my past I have also had roles that would indicate that there are dedicated DMZ, WAN edge, Internet edge devices. So this is the place to set this.\nModule Documentation # Read the Docs GitHub This module does require pynautobot to execute properly\nEnvironment #For this demo, here are the versions shown:\nComponent Version Nautobot v1.0.0b2 Nautobot Ansible Collection v1.0.3 pynautobot 1.0.0 Data File #The roles are going to be a little more straight forward. We will only set the name, color, and if the role can be a VM or not, from the vm_role key.\n--- device_roles: - name: Firewall color: \u0026#34;FF0000\u0026#34; vm_role: true - name: Leaf color: \u0026#34;008000\u0026#34; vm_role: false - name: Router color: \u0026#34;000080\u0026#34; vm_role: true - name: Server color: \u0026#34;000000\u0026#34; vm_role: false - name: Spine color: \u0026#34;0000FF\u0026#34; vm_role: false - name: Switch color: \u0026#34;008000\u0026#34; vm_role: true - name: VM color: \u0026#34;00FFFF\u0026#34; vm_role: true Example #Example - Adding Device Roles #Running the playbook on the roles are going to be straight to the point. Before the execution Nautobot\u0026rsquo;s UI shows no device roles:\n--- - name: \u0026#34;ADD DEVICE ROLES\u0026#34; hosts: localhost connection: local gather_facts: false # No gathering facts about the container execution env tasks: - name: \u0026#34;05 - ADD DEVICE ROLES\u0026#34; # Already present, showing idempotency networktocode.nautobot.device_role: url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_URL\u0026#39;) }}\u0026#34; token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_TOKEN\u0026#39;) }}\u0026#34; data: name: \u0026#34;{{ item[\u0026#39;name\u0026#39;] }}\u0026#34; color: \u0026#34;{{ item[\u0026#39;color\u0026#39;] }}\u0026#34; vm_role: \u0026#34;{{ item[\u0026#39;vm_role\u0026#39;] }}\u0026#34; loop: \u0026#34;{{ device_roles }}\u0026#34; Example - Execution #This execution shows that all of the device types are added.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 josh-v@a6339c74e30d:~$ ansible-playbook add_device_role.yml -vv ansible-playbook 2.10.6 config file = /local/ansible.cfg configured module search path = [\u0026#39;/local/.ansible/plugins/modules\u0026#39;, \u0026#39;/usr/share/ansible/plugins/modules\u0026#39;] ansible python module location = /usr/local/lib/python3.7/site-packages/ansible executable location = /usr/local/bin/ansible-playbook python version = 3.7.10 (default, Feb 16 2021, 19:28:34) [GCC 8.3.0] Using /local/ansible.cfg as config file [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; redirecting (type: callback) ansible.builtin.yaml to community.general.yaml redirecting (type: callback) ansible.builtin.yaml to community.general.yaml Skipping callback \u0026#39;default\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;minimal\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;oneline\u0026#39;, as we already have a stdout callback. PLAYBOOK: add_device_role.yml ********************************************************************************************** 1 plays in add_device_role.yml PLAY [ADD DEVICE ROLES] **************************************************************************************************** META: ran handlers TASK [05 - ADD DEVICE ROLES] *********************************************************************************************** task path: /local/add_device_role.yml:7 changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Firewall\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;FF0000\u0026#39;, \u0026#39;vm_role\u0026#39;: True}) =\u0026gt; changed=true ansible_loop_var: item device_role: color: ff0000 created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; id: 252bd7ba-3b15-4651-a7e3-e43cdd85227c last_updated: \u0026#39;2021-03-14T18:50:53.994175Z\u0026#39; name: Firewall slug: firewall url: http://nautobot-demo.josh-v.com/api/dcim/device-roles/252bd7ba-3b15-4651-a7e3-e43cdd85227c/ vm_role: true item: color: FF0000 name: Firewall vm_role: true msg: device_role Firewall created changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Leaf\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;008000\u0026#39;, \u0026#39;vm_role\u0026#39;: False}) =\u0026gt; changed=true ansible_loop_var: item device_role: color: 008000 created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; id: 35f176c4-9b20-4e3c-b961-47c624afaa56 last_updated: \u0026#39;2021-03-14T18:50:54.942372Z\u0026#39; name: Leaf slug: leaf url: http://nautobot-demo.josh-v.com/api/dcim/device-roles/35f176c4-9b20-4e3c-b961-47c624afaa56/ vm_role: false item: color: 008000 name: Leaf vm_role: false msg: device_role Leaf created changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Router\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;000080\u0026#39;, \u0026#39;vm_role\u0026#39;: True}) =\u0026gt; changed=true ansible_loop_var: item device_role: color: 000080 created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; id: c6909cfd-0fd9-4ab1-b0e7-58493fff84b7 last_updated: \u0026#39;2021-03-14T18:50:55.901588Z\u0026#39; name: Router slug: router url: http://nautobot-demo.josh-v.com/api/dcim/device-roles/c6909cfd-0fd9-4ab1-b0e7-58493fff84b7/ vm_role: true item: color: 000080 name: Router vm_role: true msg: device_role Router created changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Server\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;000000\u0026#39;, \u0026#39;vm_role\u0026#39;: False}) =\u0026gt; changed=true ansible_loop_var: item device_role: color: \u0026#39;000000\u0026#39; created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; id: f9acf678-7b71-4cf9-88f8-4e3ad3f499cb last_updated: \u0026#39;2021-03-14T18:50:57.004599Z\u0026#39; name: Server slug: server url: http://nautobot-demo.josh-v.com/api/dcim/device-roles/f9acf678-7b71-4cf9-88f8-4e3ad3f499cb/ vm_role: false item: color: \u0026#39;000000\u0026#39; name: Server vm_role: false msg: device_role Server created changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Spine\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;0000FF\u0026#39;, \u0026#39;vm_role\u0026#39;: False}) =\u0026gt; changed=true ansible_loop_var: item device_role: color: 0000ff created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; id: 4121b0bf-8085-424e-bf0d-b11855cd9c04 last_updated: \u0026#39;2021-03-14T18:50:57.912158Z\u0026#39; name: Spine slug: spine url: http://nautobot-demo.josh-v.com/api/dcim/device-roles/4121b0bf-8085-424e-bf0d-b11855cd9c04/ vm_role: false item: color: 0000FF name: Spine vm_role: false msg: device_role Spine created changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Switch\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;008000\u0026#39;, \u0026#39;vm_role\u0026#39;: True}) =\u0026gt; changed=true ansible_loop_var: item device_role: color: 008000 created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; id: c99fe14b-5172-446f-8bd5-78c1e84aaa1c last_updated: \u0026#39;2021-03-14T18:50:58.743836Z\u0026#39; name: Switch slug: switch url: http://nautobot-demo.josh-v.com/api/dcim/device-roles/c99fe14b-5172-446f-8bd5-78c1e84aaa1c/ vm_role: true item: color: 008000 name: Switch vm_role: true msg: device_role Switch created changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;VM\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;00FFFF\u0026#39;, \u0026#39;vm_role\u0026#39;: True}) =\u0026gt; changed=true ansible_loop_var: item device_role: color: 00ffff created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; id: 00a2da7f-fe44-4332-bc58-391b429c97eb last_updated: \u0026#39;2021-03-14T18:50:59.548621Z\u0026#39; name: VM slug: vm url: http://nautobot-demo.josh-v.com/api/dcim/device-roles/00a2da7f-fe44-4332-bc58-391b429c97eb/ vm_role: true item: color: 00FFFF name: VM vm_role: true msg: device_role VM created META: ran handlers META: ran handlers PLAY RECAP ***************************************************************************************************************** localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 After completion of this you will have the device roles are now available to be assigned to devices. Taking a look the UI now has the data:\nSummary #Device roles are a required item to add devices to Nautobot. This can be as generic as \u0026ldquo;Device\u0026rdquo; or \u0026ldquo;Network Device\u0026rdquo;. However, I strongly encourage you to look at putting some thought into the roles that you will assign to devices. This will become very helpful in the future as you look at building out the automation platform. You can see in the inventory build, you can assign devices based on roles to an inventory group. This becomes particularly helpful when you want to run a playbook against a single group, such as all Leaf switches, or all Spine switches that must have a particular configuration set.\nHope this has helped. If so, let me know with a comment below or give a thumbs up on the post. Connect with me on Twitter @vanderaaj.\n","date":"2021-03-14","permalink":"https://josh-v.com/nautobot-ansible-device-roles/","section":"Posts","summary":"\u003cp\u003eA device role is aptly named, the role of the device. This is likely to be something that is meaningful to your organization and could change. For example you may have the 3 tier system of Core, Distribution, and Access layer environments. These are just fine. So you would want to have the roles there to reflect this reality. You may have leaf-spine environments, there are two more roles. And in my past I have also had roles that would indicate that there are dedicated DMZ, WAN edge, Internet edge devices. So this is the place to set this.\u003c/p\u003e","title":"Nautobot Ansible Collection: Device Roles"},{"content":"Platforms are an optional item when adding devices into Nautobot. The platform is the OS that you are going to be using. Most often this is used to help identify which driver your automation platform is going to be using. Specifically the slug of the platform is what needs to match. So in the terms of Ansible (since we are using Ansible to populate Nautobot), you will want to set Cisco IOS devices to ios. By having the slug match the automation platform name you have that information in your inventory. For these reasons I strongly recommend setting the Platform for devices.\nModule Documentation # Read the Docs GitHub This module does require pynautobot to execute properly\nEnvironment #For this demo, here are the versions shown:\nComponent Version Nautobot v1.0.0b2 Nautobot Ansible Collection v1.0.3 pynautobot 1.0.0 Data File #Now that you may want to have a different slug than what is displayed, the data structure is getting slightly more complex than the manufacturers file. There will be a list of dictionaries, where the dictionary has three keys: name, slug, and manufacturer. Because in this series the platform is going to be tied to the manufacturer, the manufacturer is the next item in the list to get added after the site.\n--- platforms: - name: Arista EOS slug: eos manufacturer: Arista - name: Cisco IOS slug: ios manufacturer: Cisco - name: JUNOS slug: junos manufacturer: Juniper Example #Example - Adding Platform #Getting started I already have a Cisco manufacturer included from a different demo. This will not hurt what is being demonstrated here. The task to add a manufacturer looks like:\n--- - name: \u0026#34;ADD PLATFORMS\u0026#34; hosts: localhost connection: local gather_facts: false # No gathering facts about the container execution env tasks: - name: \u0026#34;05 - ADD PLATFORMS\u0026#34; networktocode.nautobot.platform: url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_URL\u0026#39;) }}\u0026#34; token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_TOKEN\u0026#39;) }}\u0026#34; data: name: \u0026#34;{{ item[\u0026#39;name\u0026#39;] }}\u0026#34; slug: \u0026#34;{{ item[\u0026#39;slug\u0026#39;] }}\u0026#34; manufacturer: \u0026#34;{{ item[\u0026#39;manufacturer\u0026#39;] }}\u0026#34; loop: \u0026#34;{{ platforms }}\u0026#34; Before the execution there are no platforms showing in Nautobot.\nExample - Execution #This execution shows that all of the platforms are added.\njosh-v@a6339c74e30d:~$ ansible-playbook add_platforms.yml -vv ansible-playbook 2.10.6 config file = /local/ansible.cfg configured module search path = [\u0026#39;/local/.ansible/plugins/modules\u0026#39;, \u0026#39;/usr/share/ansible/plugins/modules\u0026#39;] ansible python module location = /usr/local/lib/python3.7/site-packages/ansible executable location = /usr/local/bin/ansible-playbook python version = 3.7.10 (default, Feb 16 2021, 19:28:34) [GCC 8.3.0] Using /local/ansible.cfg as config file [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; redirecting (type: callback) ansible.builtin.yaml to community.general.yaml redirecting (type: callback) ansible.builtin.yaml to community.general.yaml Skipping callback \u0026#39;default\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;minimal\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;oneline\u0026#39;, as we already have a stdout callback. PLAYBOOK: add_platforms.yml ************************************************************************************************ 1 plays in add_platforms.yml PLAY [ADD PLATFORMS] ******************************************************************************************************* META: ran handlers TASK [05 - ADD PLATFORMS] ************************************************************************************************** task path: /local/add_platforms.yml:7 changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Arista EOS\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;eos\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Arista\u0026#39;}) =\u0026gt; changed=true ansible_loop_var: item item: manufacturer: Arista name: Arista EOS slug: eos msg: platform Arista EOS created platform: created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; id: 1d6b8698-c709-49f7-921e-d4a4c85f3317 last_updated: \u0026#39;2021-03-14T18:33:18.255025Z\u0026#39; manufacturer: fdead7a3-58a6-4a62-bb52-e21bbe2c6bf7 name: Arista EOS napalm_args: null napalm_driver: \u0026#39;\u0026#39; slug: eos url: http://nautobot-demo.josh-v.com/api/dcim/platforms/1d6b8698-c709-49f7-921e-d4a4c85f3317/ changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Cisco IOS\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;ios\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;}) =\u0026gt; changed=true ansible_loop_var: item item: manufacturer: Cisco name: Cisco IOS slug: ios msg: platform Cisco IOS created platform: created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; id: 96d58cc1-ad7e-43c7-a79f-db748e6eb894 last_updated: \u0026#39;2021-03-14T18:33:19.081567Z\u0026#39; manufacturer: 58c56ff8-f507-4356-9b6f-915be289831b name: Cisco IOS napalm_args: null napalm_driver: \u0026#39;\u0026#39; slug: ios url: http://nautobot-demo.josh-v.com/api/dcim/platforms/96d58cc1-ad7e-43c7-a79f-db748e6eb894/ changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;JUNOS\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;junos\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Juniper\u0026#39;}) =\u0026gt; changed=true ansible_loop_var: item item: manufacturer: Juniper name: JUNOS slug: junos msg: platform JUNOS created platform: created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; id: a6abfcb8-3ec9-4067-9aca-e88f9fa8eb87 last_updated: \u0026#39;2021-03-14T18:33:19.884201Z\u0026#39; manufacturer: 3aa03612-10d9-41e6-81ad-90a0d52fe03a name: JUNOS napalm_args: null napalm_driver: \u0026#39;\u0026#39; slug: junos url: http://nautobot-demo.josh-v.com/api/dcim/platforms/a6abfcb8-3ec9-4067-9aca-e88f9fa8eb87/ META: ran handlers META: ran handlers PLAY RECAP ***************************************************************************************************************** localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Now you see all of the defined platforms showing up in Nautobot.\nExample - Idempotency #Showing the idempotency of the module, on the second run there are no changes made.\njosh-v@a6339c74e30d:~$ ansible-playbook add_platforms.yml -vv ansible-playbook 2.10.6 config file = /local/ansible.cfg configured module search path = [\u0026#39;/local/.ansible/plugins/modules\u0026#39;, \u0026#39;/usr/share/ansible/plugins/modules\u0026#39;] ansible python module location = /usr/local/lib/python3.7/site-packages/ansible executable location = /usr/local/bin/ansible-playbook python version = 3.7.10 (default, Feb 16 2021, 19:28:34) [GCC 8.3.0] Using /local/ansible.cfg as config file [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; redirecting (type: callback) ansible.builtin.yaml to community.general.yaml redirecting (type: callback) ansible.builtin.yaml to community.general.yaml Skipping callback \u0026#39;default\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;minimal\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;oneline\u0026#39;, as we already have a stdout callback. PLAYBOOK: add_platforms.yml ************************************************************************************************ 1 plays in add_platforms.yml PLAY [ADD PLATFORMS] ******************************************************************************************************* META: ran handlers TASK [05 - ADD PLATFORMS] ************************************************************************************************** task path: /local/add_platforms.yml:7 ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Arista EOS\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;eos\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Arista\u0026#39;}) =\u0026gt; changed=false ansible_loop_var: item item: manufacturer: Arista name: Arista EOS slug: eos msg: platform Arista EOS already exists platform: created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; device_count: 0 id: 1d6b8698-c709-49f7-921e-d4a4c85f3317 last_updated: \u0026#39;2021-03-14T18:33:18.255025Z\u0026#39; manufacturer: fdead7a3-58a6-4a62-bb52-e21bbe2c6bf7 name: Arista EOS napalm_args: null napalm_driver: \u0026#39;\u0026#39; slug: eos url: http://nautobot-demo.josh-v.com/api/dcim/platforms/1d6b8698-c709-49f7-921e-d4a4c85f3317/ virtualmachine_count: 0 ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Cisco IOS\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;ios\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;}) =\u0026gt; changed=false ansible_loop_var: item item: manufacturer: Cisco name: Cisco IOS slug: ios msg: platform Cisco IOS already exists platform: created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; device_count: 0 id: 96d58cc1-ad7e-43c7-a79f-db748e6eb894 last_updated: \u0026#39;2021-03-14T18:33:19.081567Z\u0026#39; manufacturer: 58c56ff8-f507-4356-9b6f-915be289831b name: Cisco IOS napalm_args: null napalm_driver: \u0026#39;\u0026#39; slug: ios url: http://nautobot-demo.josh-v.com/api/dcim/platforms/96d58cc1-ad7e-43c7-a79f-db748e6eb894/ virtualmachine_count: 0 ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;JUNOS\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;junos\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Juniper\u0026#39;}) =\u0026gt; changed=false ansible_loop_var: item item: manufacturer: Juniper name: JUNOS slug: junos msg: platform JUNOS already exists platform: created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; device_count: 0 id: a6abfcb8-3ec9-4067-9aca-e88f9fa8eb87 last_updated: \u0026#39;2021-03-14T18:33:19.884201Z\u0026#39; manufacturer: 3aa03612-10d9-41e6-81ad-90a0d52fe03a name: JUNOS napalm_args: null napalm_driver: \u0026#39;\u0026#39; slug: junos url: http://nautobot-demo.josh-v.com/api/dcim/platforms/a6abfcb8-3ec9-4067-9aca-e88f9fa8eb87/ virtualmachine_count: 0 META: ran handlers META: ran handlers PLAY RECAP ***************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Summary #Platforms are one of the items that you will strongly want to get updated into Nautobot. By associating a device with a platform you can then use it in the inventory plugins to identify things such as the ansible_network_os dynamically. Need to have a new platform to test things with, just create a new platform, change a few settings, and the information is dynamically available within your playbooks.\nHope this has helped. If so, let me know with a comment below or give a thumbs up on the post. You can leave a comment or connect with me on Twitter as well, at @vanderaaj.\nThanks,\nJosh\n","date":"2021-03-14","permalink":"https://josh-v.com/nautobot-ansible-platforms/","section":"Posts","summary":"\u003cp\u003ePlatforms are an optional item when adding devices into Nautobot. The platform is the OS that you are going to be using. Most often this is used to help identify which driver your automation platform is going to be using. Specifically the slug of the platform is what needs to match. So in the terms of Ansible (since we are using Ansible to populate Nautobot), you will want to set Cisco IOS devices to \u003cstrong\u003eios\u003c/strong\u003e. By having the slug match the automation platform name you have that information in your inventory. For these reasons I strongly recommend setting the Platform for devices.\u003c/p\u003e","title":"Nautobot Ansible Collection: Platforms"},{"content":"Adding your manufacturers via code is the easy way to get started with your Nautobot devices. Immediately after adding Sites, the next thing to get going when using Nautobot as your Source of Truth is to add in Manufacturers. These are just that, who makes the gear that you use. For this demonstration you will see adding just a few manufacturers. I\u0026rsquo;m not necessarily picking on any vendors and who should or shouldn\u0026rsquo;t be here. It is just what my background brings.\nModule Documentation # Read the Docs GitHub This module does require pynautobot to execute properly\nEnvironment #For this demo, here are the versions shown:\nComponent Version Nautobot v1.0.0b2 Nautobot Ansible Collection v1.0.3 pynautobot 1.0.0 Data File #The documentation indicates that there are two parameters, name and slug. I\u0026rsquo;m not going to modify the slug in any way for these as the auto-generated slug is just fine. Because of this, the demo will not have a more complex variable, just a list of manufacturers.\n--- manufacturers: - Arista - Cisco - Juniper Example #Example - Adding Devices #Getting started I already have a Cisco manufacturer included from a different demo. This will not hurt what is being demonstrated here. The task to add a manufacturer looks like:\n--- - name: \u0026#34;SETUP MANUFACTURERS\u0026#34; hosts: localhost connection: local gather_facts: false # No gathering facts about the container execution env tasks: - name: \u0026#34;05 - ADD MANUFACTURERS\u0026#34; networktocode.nautobot.manufacturer: url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_URL\u0026#39;) }}\u0026#34; token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_TOKEN\u0026#39;) }}\u0026#34; data: name: \u0026#34;{{ item }}\u0026#34; loop: \u0026#34;{{ manufacturers }}\u0026#34; Here is the before:\nExample - Execution #Pretty short and sweet on this playbook. With having Cisco already present, you can see that the module is idempotent:\njosh-v@a6339c74e30d:~$ ansible-playbook add_manufacturers.yml -vv ansible-playbook 2.10.6 config file = /local/ansible.cfg configured module search path = [\u0026#39;/local/.ansible/plugins/modules\u0026#39;, \u0026#39;/usr/share/ansible/plugins/modules\u0026#39;] ansible python module location = /usr/local/lib/python3.7/site-packages/ansible executable location = /usr/local/bin/ansible-playbook python version = 3.7.10 (default, Feb 16 2021, 19:28:34) [GCC 8.3.0] Using /local/ansible.cfg as config file [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; redirecting (type: callback) ansible.builtin.yaml to community.general.yaml redirecting (type: callback) ansible.builtin.yaml to community.general.yaml Skipping callback \u0026#39;default\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;minimal\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;oneline\u0026#39;, as we already have a stdout callback. PLAYBOOK: add_manufacturers.yml ******************************************************************************************** 1 plays in add_manufacturers.yml PLAY [SETUP MANUFACTURERS] ************************************************************************************************* META: ran handlers TASK [05 - ADD MANUFACTURERS] ********************************************************************************************** task path: /local/add_manufacturers.yml:7 changed: [localhost] =\u0026gt; (item=Arista) =\u0026gt; changed=true ansible_loop_var: item item: Arista manufacturer: created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; id: fdead7a3-58a6-4a62-bb52-e21bbe2c6bf7 last_updated: \u0026#39;2021-03-14T17:43:35.202049Z\u0026#39; name: Arista slug: arista url: http://nautobot-demo.josh-v.com/api/dcim/manufacturers/fdead7a3-58a6-4a62-bb52-e21bbe2c6bf7/ msg: manufacturer Arista created changed: [localhost] =\u0026gt; (item=Cisco) =\u0026gt; changed=true ansible_loop_var: item item: Cisco manufacturer: created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; id: 58c56ff8-f507-4356-9b6f-915be289831b last_updated: \u0026#39;2021-03-14T17:43:36.293444Z\u0026#39; name: Cisco slug: cisco url: http://nautobot-demo.josh-v.com/api/dcim/manufacturers/58c56ff8-f507-4356-9b6f-915be289831b/ msg: manufacturer Cisco created changed: [localhost] =\u0026gt; (item=Juniper) =\u0026gt; changed=true ansible_loop_var: item item: Juniper manufacturer: created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; id: 3aa03612-10d9-41e6-81ad-90a0d52fe03a last_updated: \u0026#39;2021-03-14T17:43:37.481073Z\u0026#39; name: Juniper slug: juniper url: http://nautobot-demo.josh-v.com/api/dcim/manufacturers/3aa03612-10d9-41e6-81ad-90a0d52fe03a/ msg: manufacturer Juniper created META: ran handlers META: ran handlers PLAY RECAP ***************************************************************************************************************** localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Example - Idempotency #Give this a second run and the playbook shows everything coming back as OK, without any changes.\njosh-v@a6339c74e30d:~$ ansible-playbook add_manufacturers.yml -vv ansible-playbook 2.10.6 config file = /local/ansible.cfg configured module search path = [\u0026#39;/local/.ansible/plugins/modules\u0026#39;, \u0026#39;/usr/share/ansible/plugins/modules\u0026#39;] ansible python module location = /usr/local/lib/python3.7/site-packages/ansible executable location = /usr/local/bin/ansible-playbook python version = 3.7.10 (default, Feb 16 2021, 19:28:34) [GCC 8.3.0] Using /local/ansible.cfg as config file [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; redirecting (type: callback) ansible.builtin.yaml to community.general.yaml redirecting (type: callback) ansible.builtin.yaml to community.general.yaml Skipping callback \u0026#39;default\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;minimal\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;oneline\u0026#39;, as we already have a stdout callback. PLAYBOOK: add_manufacturers.yml ******************************************************************************************** 1 plays in add_manufacturers.yml PLAY [SETUP MANUFACTURERS] ************************************************************************************************* META: ran handlers TASK [05 - ADD MANUFACTURERS] ********************************************************************************************** task path: /local/add_manufacturers.yml:7 ok: [localhost] =\u0026gt; (item=Arista) =\u0026gt; changed=false ansible_loop_var: item item: Arista manufacturer: created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; devicetype_count: 0 id: fdead7a3-58a6-4a62-bb52-e21bbe2c6bf7 inventoryitem_count: 0 last_updated: \u0026#39;2021-03-14T17:43:35.202049Z\u0026#39; name: Arista platform_count: 0 slug: arista url: http://nautobot-demo.josh-v.com/api/dcim/manufacturers/fdead7a3-58a6-4a62-bb52-e21bbe2c6bf7/ msg: manufacturer Arista already exists ok: [localhost] =\u0026gt; (item=Cisco) =\u0026gt; changed=false ansible_loop_var: item item: Cisco manufacturer: created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; devicetype_count: 0 id: 58c56ff8-f507-4356-9b6f-915be289831b inventoryitem_count: 0 last_updated: \u0026#39;2021-03-14T17:43:36.293444Z\u0026#39; name: Cisco platform_count: 0 slug: cisco url: http://nautobot-demo.josh-v.com/api/dcim/manufacturers/58c56ff8-f507-4356-9b6f-915be289831b/ msg: manufacturer Cisco already exists ok: [localhost] =\u0026gt; (item=Juniper) =\u0026gt; changed=false ansible_loop_var: item item: Juniper manufacturer: created: \u0026#39;2021-03-14\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; devicetype_count: 0 id: 3aa03612-10d9-41e6-81ad-90a0d52fe03a inventoryitem_count: 0 last_updated: \u0026#39;2021-03-14T17:43:37.481073Z\u0026#39; name: Juniper platform_count: 0 slug: juniper url: http://nautobot-demo.josh-v.com/api/dcim/manufacturers/3aa03612-10d9-41e6-81ad-90a0d52fe03a/ msg: manufacturer Juniper already exists META: ran handlers META: ran handlers PLAY RECAP ***************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Now you see all of them showing up in Nautobot.\nSummary #The manufacturers are a base to adding devices. To add/sync manufacturers you can leverage the Nautobot Ansible Collection for manufacturers to get this data synced. To have your entire environment completely automated with using Ansible, this is a great solution.\n","date":"2021-03-14","permalink":"https://josh-v.com/nautobot-ansible-manufacturers/","section":"Posts","summary":"\u003cp\u003eAdding your manufacturers via code is the easy way to get started with your Nautobot devices. Immediately after adding Sites, the next thing to get going when using Nautobot as your Source of Truth is to add in Manufacturers. These are just that, who makes the gear that you use. For this demonstration you will see adding just a few manufacturers. I\u0026rsquo;m not necessarily picking on any vendors and who should or shouldn\u0026rsquo;t be here. It is just what my background brings.\u003c/p\u003e","title":"Nautobot Ansible Collection: Manufacturers"},{"content":"This post dives into the Nautobot Ansible Content Collection sites module to create/update a Site. This series for the beginning will be a clone of what I had done previously with NetBox. So some of the language will be very similar.\nWhen it comes to creating and deleting sites in Nautobot, the question of should I be using Ansible to do this? In my opinion this is a yes it should be. Most likely an IT tool is not the tool that will be the Source of Truth as it comes to physical sites involved in an organization. So this module in particular that should be looked at and put into production use with Ansible.\nEnvironment #For this demo, here are the versions shown:\nComponent Version Nautobot v1.0.0b2 Nautobot Ansible Collection v1.0.2 pynautobot 1.0.0 Site Module #Within Nautobot, the site is the most basic unit, and is required for devices to be added. This is the first thing that you should do when creating a Nautobot instance is to start to build out sites. There are a many set of parameters that you can add to your sites, but the minimum required are:\nname: The name of the site Take a look at the documentation for all of the additional parameters. The ones that stick out to me (and there are many more) include:\nasn: The BGP AS Number contact name \u0026amp; email: Site contact information physical and shipping addresses tags time_zone Examples #In this getting started demos I will be showing how things look with demo data maintained within YAML. In reality this is data that would be sourced from some other location, hopefully able to be gathered via an API. The following data will be added to the environment:\n--- sites: - name: MSP time_zone: America/Chicago status: active description: Minneapolis - name: DEN time_zone: America/Denver status: active description: Denver - name: NYC time_zone: America/New_York status: active description: New York - name: PDX time_zone: America/Los_Angeles status: active description: Portland - name: GRB time_zone: America/Chicago status: active description: Green Bay - name: MCO time_zone: America/New_York status: active description: Orlando Running the following playbook multiple times will show that the module itself is idempotent in that it will not keep creating sites.\nYou will notice one difference with the module compared to the NetBox collections. Instead of specifying nautobot everywhere, one just needs to put what it is that is being referenced. In this example instead of nautobot_site, it is site. And instead of nautobot_token or nautobot_url, it is just token and url.\n--- - name: \u0026#34;SETUP SITES\u0026#34; hosts: localhost connection: local gather_facts: no tasks: - name: \u0026#34;10 - SETUP SITES\u0026#34; networktocode.nautobot.site: url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_URL\u0026#39;) }}\u0026#34; token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_TOKEN\u0026#39;) }}\u0026#34; data: \u0026#34;{{ site }}\u0026#34; state: present validate_certs: False loop: \u0026#34;{{ sites }}\u0026#34; loop_control: loop_var: site label: \u0026#34;{{ site[\u0026#39;name\u0026#39;] }}\u0026#34; - name: \u0026#34;20 - REMOVE CLOSED SITES\u0026#34; when: \u0026#34;closed_sites is defined\u0026#34; networktocode.nautobot.site: url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_URL\u0026#39;) }}\u0026#34; token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NAUTOBOT_TOKEN\u0026#39;) }}\u0026#34; data: \u0026#34;{{ site }}\u0026#34; state: absent validate_certs: False loop: \u0026#34;{{ closed_sites }}\u0026#34; loop_control: loop_var: site label: \u0026#34;{{ site[\u0026#39;name\u0026#39;] }}\u0026#34; On the first run from an empty Nautobot host, there are changes for each of the sites:\njosh-v@a6339c74e30d:~$ ansible-playbook add_sites.yml -vv ansible-playbook 2.10.6 config file = /local/ansible.cfg configured module search path = [\u0026#39;/local/.ansible/plugins/modules\u0026#39;, \u0026#39;/usr/share/ansible/plugins/modules\u0026#39;] ansible python module location = /usr/local/lib/python3.7/site-packages/ansible executable location = /usr/local/bin/ansible-playbook python version = 3.7.10 (default, Feb 16 2021, 19:28:34) [GCC 8.3.0] Using /local/ansible.cfg as config file [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; redirecting (type: callback) ansible.builtin.yaml to community.general.yaml redirecting (type: callback) ansible.builtin.yaml to community.general.yaml Skipping callback \u0026#39;default\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;minimal\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;oneline\u0026#39;, as we already have a stdout callback. PLAYBOOK: add_sites.yml **************************************************************************************************** 1 plays in add_sites.yml PLAY [SETUP SITES] ********************************************************************************************************* META: ran handlers TASK [10 - SETUP SITES] **************************************************************************************************** task path: /local/add_sites.yml:7 changed: [localhost] =\u0026gt; (item=MSP) =\u0026gt; changed=true ansible_loop_var: site msg: site MSP created site: description: Minneapolis name: MSP status: active time_zone: America/Chicago changed: [localhost] =\u0026gt; (item=DEN) =\u0026gt; changed=true ansible_loop_var: site msg: site DEN created site: description: Denver name: DEN status: active time_zone: America/Denver changed: [localhost] =\u0026gt; (item=NYC) =\u0026gt; changed=true ansible_loop_var: site msg: site NYC created site: description: New York name: NYC status: active time_zone: America/New_York changed: [localhost] =\u0026gt; (item=PDX) =\u0026gt; changed=true ansible_loop_var: site msg: site PDX created site: description: Portland name: PDX status: active time_zone: America/Los_Angeles changed: [localhost] =\u0026gt; (item=GRB) =\u0026gt; changed=true ansible_loop_var: site msg: site GRB created site: description: Green Bay name: GRB status: active time_zone: America/Chicago changed: [localhost] =\u0026gt; (item=MCO) =\u0026gt; changed=true ansible_loop_var: site msg: site MCO created site: description: Orlando name: MCO status: active time_zone: America/New_York TASK [20 - REMOVE CLOSED SITES] ******************************************************************************************** task path: /local/add_sites.yml:19 skipping: [localhost] =\u0026gt; changed=false skip_reason: Conditional result was False META: ran handlers META: ran handlers PLAY RECAP ***************************************************************************************************************** localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 The output below is from a second run and shows that all of the sites already exist and that the idempotency is working well.\njosh-v@a6339c74e30d:~$ ansible-playbook add_sites.yml -vv ansible-playbook 2.10.6 config file = /local/ansible.cfg configured module search path = [\u0026#39;/local/.ansible/plugins/modules\u0026#39;, \u0026#39;/usr/share/ansible/plugins/modules\u0026#39;] ansible python module location = /usr/local/lib/python3.7/site-packages/ansible executable location = /usr/local/bin/ansible-playbook python version = 3.7.10 (default, Feb 16 2021, 19:28:34) [GCC 8.3.0] Using /local/ansible.cfg as config file [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; redirecting (type: callback) ansible.builtin.yaml to community.general.yaml redirecting (type: callback) ansible.builtin.yaml to community.general.yaml Skipping callback \u0026#39;default\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;minimal\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;oneline\u0026#39;, as we already have a stdout callback. PLAYBOOK: add_sites.yml **************************************************************************************************** 1 plays in add_sites.yml PLAY [SETUP SITES] ********************************************************************************************************* META: ran handlers TASK [10 - SETUP SITES] **************************************************************************************************** task path: /local/add_sites.yml:7 ok: [localhost] =\u0026gt; (item=MSP) =\u0026gt; changed=false ansible_loop_var: site msg: site MSP already exists site: description: Minneapolis name: MSP status: active time_zone: America/Chicago ok: [localhost] =\u0026gt; (item=DEN) =\u0026gt; changed=false ansible_loop_var: site msg: site DEN already exists site: description: Denver name: DEN status: active time_zone: America/Denver ok: [localhost] =\u0026gt; (item=NYC) =\u0026gt; changed=false ansible_loop_var: site msg: site NYC already exists site: description: New York name: NYC status: active time_zone: America/New_York ok: [localhost] =\u0026gt; (item=PDX) =\u0026gt; changed=false ansible_loop_var: site msg: site PDX already exists site: description: Portland name: PDX status: active time_zone: America/Los_Angeles ok: [localhost] =\u0026gt; (item=GRB) =\u0026gt; changed=false ansible_loop_var: site msg: site GRB already exists site: description: Green Bay name: GRB status: active time_zone: America/Chicago ok: [localhost] =\u0026gt; (item=MCO) =\u0026gt; changed=false ansible_loop_var: site msg: site MCO already exists site: description: Orlando name: MCO status: active time_zone: America/New_York TASK [20 - REMOVE CLOSED SITES] ******************************************************************************************** task path: /local/add_sites.yml:19 skipping: [localhost] =\u0026gt; changed=false skip_reason: Conditional result was False META: ran handlers META: ran handlers PLAY RECAP ***************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 In here we see that there was the single task showing ok and no tasks in the other sections of the play recap. The Nautobot sites page looks like:\nRemoving a site #Now let\u0026rsquo;s take a look of using the absent. I\u0026rsquo;m going to get the data by putting MCO into the closed sites key. A full production instance of this would need to first lookup the data within Nautobot, then determine which sites are open and closed. That is beyond the scope of the purpose of this blog, which is to show the executions. That is a little more logic that is very easily done in Ansible either with Ansible native tasks or from an Ansible filter or action plugin.\nThe data looks like the following:\n--- sites: - name: MSP time_zone: America/Chicago status: active description: Minneapolis - name: DEN time_zone: America/Denver status: active description: Denver - name: NYC time_zone: America/New_York status: active description: New York - name: PDX time_zone: America/Los_Angeles status: active description: Portland - name: GRB time_zone: America/Chicago status: active description: Green Bay closed_sites: - name: MCO time_zone: America/New_York status: active description: Orlando With a closed site, the next playbook run will then remove the site MCO and have the appropriate setup in place.\njosh-v@a6339c74e30d:~$ ansible-playbook add_sites.yml -vv ansible-playbook 2.10.6 config file = /local/ansible.cfg configured module search path = [\u0026#39;/local/.ansible/plugins/modules\u0026#39;, \u0026#39;/usr/share/ansible/plugins/modules\u0026#39;] ansible python module location = /usr/local/lib/python3.7/site-packages/ansible executable location = /usr/local/bin/ansible-playbook python version = 3.7.10 (default, Feb 16 2021, 19:28:34) [GCC 8.3.0] Using /local/ansible.cfg as config file [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; redirecting (type: callback) ansible.builtin.yaml to community.general.yaml redirecting (type: callback) ansible.builtin.yaml to community.general.yaml Skipping callback \u0026#39;default\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;minimal\u0026#39;, as we already have a stdout callback. Skipping callback \u0026#39;oneline\u0026#39;, as we already have a stdout callback. PLAYBOOK: add_sites.yml **************************************************************************************************** 1 plays in add_sites.yml PLAY [SETUP SITES] ********************************************************************************************************* META: ran handlers TASK [10 - SETUP SITES] **************************************************************************************************** task path: /local/add_sites.yml:7 ok: [localhost] =\u0026gt; (item=MSP) =\u0026gt; changed=false ansible_loop_var: site msg: site MSP already exists site: description: Minneapolis name: MSP status: active time_zone: America/Chicago ok: [localhost] =\u0026gt; (item=DEN) =\u0026gt; changed=false ansible_loop_var: site msg: site DEN already exists site: description: Denver name: DEN status: active time_zone: America/Denver ok: [localhost] =\u0026gt; (item=NYC) =\u0026gt; changed=false ansible_loop_var: site msg: site NYC already exists site: description: New York name: NYC status: active time_zone: America/New_York ok: [localhost] =\u0026gt; (item=PDX) =\u0026gt; changed=false ansible_loop_var: site msg: site PDX already exists site: description: Portland name: PDX status: active time_zone: America/Los_Angeles ok: [localhost] =\u0026gt; (item=GRB) =\u0026gt; changed=false ansible_loop_var: site msg: site GRB already exists site: description: Green Bay name: GRB status: active time_zone: America/Chicago TASK [20 - REMOVE CLOSED SITES] ******************************************************************************************** task path: /local/add_sites.yml:19 changed: [localhost] =\u0026gt; (item=MCO) =\u0026gt; changed=true ansible_loop_var: site msg: site MCO deleted site: description: Orlando name: MCO status: active time_zone: America/New_York META: ran handlers META: ran handlers PLAY RECAP ***************************************************************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Note that TASK 20 now is not skipped. This runs and removes the site with a message that the site was deleted. This is once again idempotent, with the capability to run time and again without causing any changes unless changes are required.\nSummary #This module is a very good module with a lot of options to get you started. This is absolutely a module that I would become familiar with as your organization is changing over time. This will allow you to keep your Nautobot environment up to date with the site changes as you get new and closed sites alike. Hopefully this has been helpful to demonstrate it\u0026rsquo;s capabilities. Let me know your comments below, or give it a thumbs up if you have found this helpful.\nLet me know what you think! Comment below or you can find me on Twitter https://twitter.com/vanderaaj/.\nThanks,\nJosh\n","date":"2021-03-13","permalink":"https://josh-v.com/nautobot-ansible-sites/","section":"Posts","summary":"\u003cp\u003eThis post dives into the \u003ca href=\"https://nautobot-ansible.readthedocs.io/en/latest/\" target=\"_blank\" rel=\"noreferrer\"\u003eNautobot Ansible Content Collection\u003c/a\u003e sites module to create/update a \u003ca href=\"https://nautobot-ansible.readthedocs.io/en/latest/plugins/site_module.html\" target=\"_blank\" rel=\"noreferrer\"\u003eSite\u003c/a\u003e. This series for the beginning will be a clone of what I had done previously with NetBox. So some of the language will be very similar.\u003c/p\u003e\n\u003cp\u003eWhen it comes to creating and deleting sites in Nautobot, the question of should I be using Ansible to do this? In my opinion this is a \u003cstrong\u003eyes it should be\u003c/strong\u003e. Most likely an IT tool is not the tool that will be the Source of Truth as it comes to physical sites involved in an organization. So this module in particular that should be looked at and put into production use with Ansible.\u003c/p\u003e","title":"Nautobot Ansible Collection: Site Module"},{"content":"This is the first post as I shift into taking a closer look at the Nautobot Ansible Collection. The collection includes many of the needed modules to effectively manage your Nautobot environment. If This will take a deeper dive into several of the components of the inventory plugin, but not all of the options. The documentation for all of the collection can be found at:\nReadTheDocs: https://nautobot-ansible.readthedocs.io Galaxy Page: https://galaxy.ansible.com/networktocode/nautobot This post is going to give information on how to install the collection as it may be applicable to every post in the series (as they get posted).\nIf you were a user of the NetBox Ansible Collection previously, you will notice a few differences. The first big difference in the modules is that there is no preface of nautobot_ before each module. Since this Collection is developed after Ansible 2.10 they are using the FQCN (Fully Qualified Collection Name), there is no longer the need to prefix the name to the module name. So where there was a netbox_device before it will now be just device, underneath the FQCN of networktocode.nautobot.device as an example.\nInstallation #Installation is done via Ansible Galaxy. It is recommended to have the latest version of the collection when working on it as there are updates happening routinely. There is a Python requirement with many the modules of the pynautobot Python package.\nIt does not matter which order you install these in, you just need to install both before you start using the module.\nInstallation - pynautobot #To install you execute the following to get the latest version of pynautobot:\npip install pynautobot --upgrade Installation - Nautobot Collection #The collection is installed via Ansible Galaxy as a primary method to install. You can also install the collection manually from GitHub, but the galaxy method is the preferred method.\nansible-galaxy collection install networktocode.nautobot If you add on --force at the end, Ansible Galaxy will install the latest version on top of what you may already have. If you already have a version of the collection installed, Galaxy will not overwrite what you already have.\nVerification of Installation #Once you have run the steps there are many ways to verify that the installation is completed successfully for the Python package. The one that I like to use is to execute a pip freeze | grep \u0026lt;package_name\u0026gt;. The execution looks like this on the current date:\npip3 freeze | grep pynautobot pynautobot==1.0.1 To verify that you have installed the Nautobot Ansible Collection, you can execute the Ansible Doc command to get the current documentation. This is done as followed with the device module to verify that the docs load:\nansible-doc networktocode.nautobot.device If the module is not installed properly you will see, with a key in on the first line\n1 2 3 $ ansible-doc networktocode.nautobot.device [WARNING]: module networktocode.nautobot.device not found in: /root/.ansible/plugins/modules:/usr/share/ansible/plugins/modules:/usr/local/lib/python3.7/site-packages/ansible/modules When the collection is installed properly you will see the following output with the command:\n\u0026gt; NETWORKTOCODE.NAUTOBOT.DEVICE (/root/.ansible/collections/ansible_collections/networktocode/nautobot/plugins/modules/device.py) Creates, updates or removes devices from Nautobot OPTIONS (= is mandatory): = data Defines the device configuration type: dict SUBOPTIONS: - asset_tag Asset tag that is associated to the device [Default: (null)] type: str - cluster Cluster that the device will be assigned to [Default: (null)] type: raw - comments Comments that may include additional information in regards to the device [Default: (null)] type: str - custom_fields must exist in Nautobot [Default: (null)] type: dict - device_role Required if `state=present\u0026#39; and the device does not exist yet [Default: (null)] type: raw - device_type Required if `state=present\u0026#39; and the device does not exist yet [Default: (null)] type: raw - face Required if `rack\u0026#39; is defined (Choices: Front, front, Rear, rear)[Default: (null)] type: str - local_context_data Arbitrary JSON data to define the devices configuration variables. [Default: (null)] type: dict = name The name of the device type: str - platform The platform of the device [Default: (null)] type: raw - position The position of the device in the rack defined above [Default: (null)] type: int - primary_ip4 Primary IPv4 address assigned to the device [Default: (null)] type: raw - primary_ip6 Primary IPv6 address assigned to the device [Default: (null)] type: raw - rack The name of the rack to assign the device to [Default: (null)] type: raw - serial Serial number of the device [Default: (null)] type: str - site Required if `state=present\u0026#39; and the device does not exist yet [Default: (null)] type: raw - status The status of the device [Default: (null)] type: raw - tags Any tags that the device may need to be associated with [Default: (null)] type: list - tenant The tenant that the device will be assigned to [Default: (null)] type: raw - vc_position Position in the assigned virtual chassis [Default: (null)] type: int - vc_priority Priority in the assigned virtual chassis [Default: (null)] type: int - virtual_chassis Virtual chassis the device will be assigned to [Default: (null)] type: raw - query_params This can be used to override the specified values in ALLOWED_QUERY_PARAMS that is defined in plugins/module_utils/utils.py and provides control to users on what may make an object unique in their environment. [Default: (null)] elements: str type: list - state Use `present\u0026#39; or `absent\u0026#39; for adding or removing. (Choices: absent, present)[Default: present] type: str = token The token created within Nautobot to authorize API access type: str = url URL of the Nautobot instance resolvable by Ansible control host type: str - validate_certs If `no\u0026#39;, SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates. [Default: True] type: raw NOTES: * Tags should be defined as a YAML list * This should be ran with connection `local\u0026#39; and hosts `localhost\u0026#39; REQUIREMENTS: pynautobot AUTHOR: Network to Code (@networktocode), David Gomez (@amb1s1) METADATA: metadata_version: \u0026#39;1.1\u0026#39; status: - preview supported_by: community VERSION_ADDED_COLLECTION: networktocode.nautobot EXAMPLES: - name: \u0026#34;Test Nautobot modules\u0026#34; connection: local hosts: localhost gather_facts: False tasks: - name: Create device within Nautobot with only required information networktocode.nautobot.device: url: http://nautobot.local token: thisIsMyToken data: name: Test Device device_type: C9410R device_role: Core Switch site: Main status: active state: present - name: Create device within Nautobot with empty string name to generate UUID networktocode.nautobot.device: url: http://nautobot.local token: thisIsMyToken data: name: \u0026#34;\u0026#34; device_type: C9410R device_role: Core Switch site: Main status: active state: present - name: Delete device within nautobot networktocode.nautobot.device: url: http://nautobot.local token: thisIsMyToken data: name: Test Device state: absent - name: Create device with tags networktocode.nautobot.device: url: http://nautobot.local token: thisIsMyToken data: name: Another Test Device device_type: C9410R device_role: Core Switch site: Main status: active local_context_data: bgp: \u0026#34;65000\u0026#34; tags: - Schnozzberry state: present - name: Update the rack and position of an existing device networktocode.nautobot.device: url: http://nautobot.local token: thisIsMyToken data: name: Test Device rack: Test Rack position: 10 face: Front state: present RETURN VALUES: - device Serialized object as created or already existent within Nautobot returned: success (when `state=present\u0026#39;) type: dict - msg Message indicating failure or info about what has been achieved returned: always type: str Summary #Overall the process for getting going with this collection is two steps, of installing the Python dependency and installing the collection via Ansible Galaxy. With these done, you are on your way to using the Nautobot Ansible Collection in your environment.\nUp Next # Ansible Inventory with Nautobot collection ","date":"2021-03-10","permalink":"https://josh-v.com/collection_install/","section":"Posts","summary":"\u003cp\u003eThis is the first post as I shift into taking a closer look at the Nautobot Ansible Collection. The collection includes many of the needed modules to effectively manage your Nautobot environment. If  This will take a deeper dive into several of the components of the \u003cstrong\u003einventory plugin\u003c/strong\u003e, but not all of the options. The documentation for all of the collection can be found at:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eReadTheDocs: \u003ca href=\"https://nautobot-ansible.readthedocs.io\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://nautobot-ansible.readthedocs.io\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eGalaxy Page: \u003ca href=\"https://galaxy.ansible.com/networktocode/nautobot\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://galaxy.ansible.com/networktocode/nautobot\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThis post is going to give information on how to install the collection as it may be applicable to every post in the series (as they get posted).\u003c/p\u003e","title":"Nautobot Ansible Collection: Installation"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/netbox/","section":"Tags","summary":"","title":"Netbox"},{"content":"All of the work through the modules thus far in the series have brought us to what we all want to see. How to get or update device information inside of NetBox. Adding of sites, device types, device roles are required to get us to this point. Now you can see how to add a device to NetBox using the netbox.netbox.netbox_device module.\nThis post was created when NetBox was an open source project used often in my automation framework. I have moved on to using Nautobot due to the project vision and providing a methodology that will drive network automation forward further. You may want to take a look at it yourself. There are many optional parameters for the module specifically. I encourage you to take a look at the module documentaation (linked below) in order to get a good sense of all of the options available. The required parameters for a device that is present are:\ndevice_role device_type name site An important caveat for me is that this is something that should be done with rarity. Only when truly adding a device to NetBox, in a programmatic way this should be used. I do not advocate for running this module constantly based on your devices. The idea is to get NetBox to be your source of truth about devices, not to have devices be the source of truth and updating NetBox.\nSo where do I see this being run? I do absolutely see it being a part of a pipeline or a service portal. The idea being that the service portal has a request for a new site to be turned up. That in turn kicks off an Ansible Playbook that will make the necessary updates to NetBox, and is done in a consistent manor.\nModule Documentation # Read the Docs GitHub This module does require pynetbox to execute properly\nEnvironment #For this demo, here are the versions shown:\nComponent Version NetBox v2.9.10 (NetBox Docker) NetBox Ansible Collection v1.1.0 pynetbox 5.1.0 Data File #The NetBox devices file is going to be a little bit more involved. In this particular demo case there are no existing inventories to use. If you want to see a demo of how to add devices to NetBox using an existing Ansible inventory, I encourage you to take a look at my GitHub repository where I did a Meetup video on working with Ansible + NetBox.\nTo simulate the idea that we are going to be running a playbook execution as part of a service request, here is the data file that will be fed to the Ansible playbook:\n# group_vars/all/devices.yml --- devices: - name: \u0026#34;grb-rtr01\u0026#34; site: \u0026#34;GRB\u0026#34; device_role: \u0026#34;Router\u0026#34; device_type: \u0026#34;IOSv\u0026#34; - name: \u0026#34;msp-rtr01\u0026#34; site: \u0026#34;MSP\u0026#34; device_role: \u0026#34;Router\u0026#34; device_type: \u0026#34;IOSv\u0026#34; Example #Example - Adding Device Types #First if you are following along with the examples thus far, I made a new site here. So in order to accommodate the new site, I added GRB and re-ran the playbook to create sites. That was done successfully and idempotently with only the GRB site being added.\n--- - name: \u0026#34;ADD DEVICES TO NETBOX\u0026#34; hosts: localhost connection: local gather_facts: false # No gathering facts about the container execution env tasks: - name: \u0026#34;05 - ADD DEVICES\u0026#34; netbox.netbox.netbox_device: netbox_url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_URL\u0026#39;) }}\u0026#34; netbox_token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; data: name: \u0026#34;{{ item[\u0026#39;name\u0026#39;] }}\u0026#34; site: \u0026#34;{{ item[\u0026#39;site\u0026#39;] }}\u0026#34; device_role: \u0026#34;{{ item[\u0026#39;device_role\u0026#39;] }}\u0026#34; device_type: \u0026#34;{{ item[\u0026#39;device_type\u0026#39;] }}\u0026#34; loop: \u0026#34;{{ devices }}\u0026#34; Example - Execution #This execution shows that all of the device types are added.\n1 2 3 4 5 6 7 8 9 10 11 12 josh-v@588715249c44:~$ ansible-playbook add_devices.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; PLAY [ADD DEVICES TO NETBOX] ************************************************************************************************************************** TASK [05 - ADD DEVICES] ******************************************************************************************************************************* changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;grb-rtr01\u0026#39;, \u0026#39;site\u0026#39;: \u0026#39;GRB\u0026#39;, \u0026#39;device_role\u0026#39;: \u0026#39;Router\u0026#39;, \u0026#39;device_type\u0026#39;: \u0026#39;IOSv\u0026#39;}) changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;msp-rtr01\u0026#39;, \u0026#39;site\u0026#39;: \u0026#39;MSP\u0026#39;, \u0026#39;device_role\u0026#39;: \u0026#39;Router\u0026#39;, \u0026#39;device_type\u0026#39;: \u0026#39;IOSv\u0026#39;}) PLAY RECAP ******************************************************************************************************************************************** localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 The second execution of playbook shows that with these three settings the module is idempotent:\n1 2 3 4 5 6 7 8 9 10 11 12 josh-v@588715249c44:~$ ansible-playbook add_devices.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; PLAY [ADD DEVICES TO NETBOX] ************************************************************************************************************************** TASK [05 - ADD DEVICES] ******************************************************************************************************************************* ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;grb-rtr01\u0026#39;, \u0026#39;site\u0026#39;: \u0026#39;GRB\u0026#39;, \u0026#39;device_role\u0026#39;: \u0026#39;Router\u0026#39;, \u0026#39;device_type\u0026#39;: \u0026#39;IOSv\u0026#39;}) ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;msp-rtr01\u0026#39;, \u0026#39;site\u0026#39;: \u0026#39;MSP\u0026#39;, \u0026#39;device_role\u0026#39;: \u0026#39;Router\u0026#39;, \u0026#39;device_type\u0026#39;: \u0026#39;IOSv\u0026#39;}) PLAY RECAP ******************************************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Now that you have some devices, you can start to do a little bit more with your NetBox environment. This playbook purposely did not add more information about the device yet, such as the serial number, interfaces, or IP addressing. This is all information that you can add more about the device as well using Ansible Facts and Resource Modules to continue to develop your source of truth. More likely to come in the future, or you can check out the content on GitHub and YouTube referenced above for immediate reference.\nSummary #Getting devices into NetBox provides a powerful place to put your source of truth for automation. It does take a small bit to get to a good place, but with a little bit of effort up front you can get things done in a consistent and repeatable fashion. No more having to do things by hand with the data points.\nHope this has helped. If so, let me know with a comment below or give a thumbs up on the post.\n","date":"2021-01-31","permalink":"https://josh-v.com/netbox-ansible-devices/","section":"Posts","summary":"\u003cp\u003eAll of the work through the modules thus far in the series have brought us to what we all want to see. How to get or update device information inside of NetBox. Adding of sites, device types, device roles are required to get us to this point. Now you can see how to add a device to NetBox using the netbox.netbox.netbox_device module.\u003c/p\u003e\n\u003cdiv class=\"flex rounded-md bg-primary-100 px-4 py-3 dark:bg-primary-900\"\u003e\n  \u003cspan class=\"pe-3 text-primary-400\"\u003e\n    \u003cspan class=\"icon relative inline-block px-1 align-text-bottom\"\u003e\u003csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003cpath fill=\"currentColor\" d=\"M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z\"/\u003e\u003c/svg\u003e\n\u003c/span\u003e\n  \u003c/span\u003e\n  \u003cspan class=\"dark:text-neutral-300\"\u003eThis post was created when NetBox was an open source project used often in my automation framework. I have moved on to using \u003ca href=\"https://www.nautobot.com\" target=\"_blank\" rel=\"noreferrer\"\u003eNautobot\u003c/a\u003e due to the project vision and providing a methodology that will drive network automation forward further. You may want to take a look at it yourself.\u003c/span\u003e\n\u003c/div\u003e\n\n\u003cp\u003eThere are many optional parameters for the module specifically. I encourage you to take a look at the module documentaation (linked below) in order to get a good sense of all of the options available. The required parameters for a device that is present are:\u003c/p\u003e","title":"NetBox Ansible Collection: Devices"},{"content":"A device role is aptly named, the role of the device. This is likely to be something that is meaningful to your organization and could change. For example you may have the 3 tier system of Core, Distribution, and Access layer environments. These are just fine. So you would want to have the roles there to reflect this reality. You may have leaf-spine environments, there are two more roles. And in my past I have also had roles that would indicate that there are dedicated DMZ, WAN edge, Internet edge devices. So this is the place to set this.\nThis post was created when NetBox was an open source project used often in my automation framework. I have moved on to using Nautobot due to the project vision and providing a methodology that will drive network automation forward further. You may want to take a look at it yourself. Module Documentation # Read the Docs GitHub This module does require pynetbox to execute properly\nOutside of the NetBox URL and Token, the data parameter has a single required parameter of name. There are only a few additional options, so those are worth mentioning here of color, slug (will be auto-generated if not), and a yes/no parameter of vm_role.\nEnvironment #For this demo, here are the versions shown:\nComponent Version NetBox v2.9.10 (NetBox Docker) NetBox Ansible Collection v2.0.0 pynetbox 5.3.1 Data File #The roles are going to be a little more straight forward. We will only set the name, color, and if the role can be a VM or not, from the vm_role key.\n--- device_roles: - name: Firewall color: \u0026#34;FF0000\u0026#34; vm_role: true - name: Leaf color: \u0026#34;008000\u0026#34; vm_role: false - name: Router color: \u0026#34;000080\u0026#34; vm_role: true - name: Server color: \u0026#34;000000\u0026#34; vm_role: false - name: Spine color: \u0026#34;0000FF\u0026#34; vm_role: false - name: Switch color: \u0026#34;008000\u0026#34; vm_role: true - name: VM color: \u0026#34;00FFFF\u0026#34; vm_role: true Example #Example - Adding Device Roles #Running the playbook on the roles are going to be straight to the point.\n--- - name: \u0026#34;ADD DEVICE ROLES TO NETBOX\u0026#34; hosts: localhost connection: local gather_facts: false # No gathering facts about the container execution env tasks: - name: \u0026#34;05 - ADD DEVICE ROLES\u0026#34; # Already present, showing idempotency netbox.netbox.netbox_device_role: netbox_url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_URL\u0026#39;) }}\u0026#34; netbox_token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; data: name: \u0026#34;{{ item[\u0026#39;name\u0026#39;] }}\u0026#34; color: \u0026#34;{{ item[\u0026#39;color\u0026#39;] }}\u0026#34; vm_role: \u0026#34;{{ item[\u0026#39;vm_role\u0026#39;] }}\u0026#34; loop: \u0026#34;{{ device_roles }}\u0026#34; Example - Execution #This execution shows that all of the device types are added.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 josh-v@588715249c44:~$ ansible-playbook add_device_role.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; PLAY [ADD DEVICE ROLES TO NETBOX] ********************************************************************************************************************* TASK [05 - ADD DEVICE ROLES] ************************************************************************************************************************** changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Firewall\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;FF0000\u0026#39;, \u0026#39;vm_role\u0026#39;: True}) changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Leaf\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;008000\u0026#39;, \u0026#39;vm_role\u0026#39;: False}) changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Router\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;000080\u0026#39;, \u0026#39;vm_role\u0026#39;: True}) changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Server\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;000000\u0026#39;, \u0026#39;vm_role\u0026#39;: False}) changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Spine\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;0000FF\u0026#39;, \u0026#39;vm_role\u0026#39;: False}) changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Switch\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;008000\u0026#39;, \u0026#39;vm_role\u0026#39;: True}) changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;VM\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;00FFFF\u0026#39;, \u0026#39;vm_role\u0026#39;: True}) PLAY RECAP ******************************************************************************************************************************************** localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 The second execution of playbook shows that with these three settings the module is idempotent:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 josh-v@588715249c44:~$ ansible-playbook add_device_role.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; PLAY [ADD DEVICE ROLES TO NETBOX] ********************************************************************************************************************* TASK [05 - ADD DEVICE ROLES] ************************************************************************************************************************** ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Firewall\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;FF0000\u0026#39;, \u0026#39;vm_role\u0026#39;: True}) ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Leaf\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;008000\u0026#39;, \u0026#39;vm_role\u0026#39;: False}) ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Router\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;000080\u0026#39;, \u0026#39;vm_role\u0026#39;: True}) ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Server\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;000000\u0026#39;, \u0026#39;vm_role\u0026#39;: False}) ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Spine\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;0000FF\u0026#39;, \u0026#39;vm_role\u0026#39;: False}) ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Switch\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;008000\u0026#39;, \u0026#39;vm_role\u0026#39;: True}) ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;VM\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;00FFFF\u0026#39;, \u0026#39;vm_role\u0026#39;: True}) PLAY RECAP ******************************************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 After completion of this you will have the device roles are now available to be assigned out.\nSummary #Device roles are a required item to add devices to NetBox. This can be as generic as \u0026ldquo;Device\u0026rdquo; or \u0026ldquo;Network Device\u0026rdquo;. However, I strongly encourage you to look at putting some thought into the roles that you will assign to devices. This will become very helpful in the future as you look at building out the automation platform. You can see in the inventory build, you can assign devices based on roles to an inventory group. This becomes particularly helpful when you want to run a playbook against a single group, such as all Leaf switches, or all Spine switches that must have a particular configuration set.\nHope this has helped. If so, let me know with a comment below or give a thumbs up on the post.\n","date":"2021-01-31","permalink":"https://josh-v.com/netbox-ansible-device-roles/","section":"Posts","summary":"\u003cp\u003eA device role is aptly named, the role of the device. This is likely to be something that is meaningful to your organization and could change. For example you may have the 3 tier system of Core, Distribution, and Access layer environments. These are just fine. So you would want to have the roles there to reflect this reality. You may have leaf-spine environments, there are two more roles. And in my past I have also had roles that would indicate that there are dedicated DMZ, WAN edge, Internet edge devices. So this is the place to set this.\u003c/p\u003e","title":"NetBox Ansible Collection: Device Roles"},{"content":"A device type is the next piece in the NetBox Device onboarding requirements. The device type corresponds to the model number of the hardware (or virtual machine). This is where you are able to template out devices during their creation. So if you have a console port on a device type, that console port will be created when you create the device. However, there is NOT a relationship built between the device type and the device. If the device type gets updated after the device is created, the device itself is not updated.\nThis post was created when NetBox was an open source project used often in my automation framework. I have moved on to using Nautobot due to the project vision and providing a methodology that will drive network automation forward further. You may want to take a look at it yourself. Module Documentation # Read the Docs GitHub This module does require pynetbox to execute properly\nEnvironment #For this demo, here are the versions shown:\nComponent Version NetBox v2.9.9 (NetBox Docker) NetBox Ansible Collection v1.1.0 pynetbox 5.1.0 Data File #This gets to be a little more of the complex data source types. There are many data parameters that are good to include. The minimum data parameter has just the model. But there are going to be many more options as you build out your NetBox environment that feeds into the data correlation that makes NetBox a pleasure to use. Such as the manufacturer that it is tied to, the part number, and the u_height as you build rack diagrams from NetBox.\nIn the demo the model, manufacturer, part number, and slug will get defined. The slug will be the lower case of the model name. The primary key is the model name in this case.\n--- device_types: - model: \u0026#34;ASAv\u0026#34; manufacturer: \u0026#34;Cisco\u0026#34; slug: \u0026#34;asav\u0026#34; part_number: \u0026#34;asav\u0026#34; - model: \u0026#34;CSR1000v\u0026#34; manufacturer: \u0026#34;Cisco\u0026#34; slug: \u0026#34;csr1000v\u0026#34; part_number: \u0026#34;csr1000v\u0026#34; - model: \u0026#34;IOSv\u0026#34; manufacturer: \u0026#34;Cisco\u0026#34; slug: \u0026#34;iosv\u0026#34; part_number: \u0026#34;iosv\u0026#34; - model: \u0026#34;nxosv\u0026#34; manufacturer: \u0026#34;Cisco\u0026#34; slug: \u0026#34;nxosv\u0026#34; part_number: \u0026#34;nxosv\u0026#34; - model: \u0026#34;vEOS\u0026#34; manufacturer: \u0026#34;Arista\u0026#34; slug: \u0026#34;veos\u0026#34; part_number: \u0026#34;veos\u0026#34; Example #Example - Adding Device Types #Getting started I already have a Cisco manufacturer included from a different demo. This will not hurt what is being demonstrated here. The task to add a manufacturer looks like:\n--- - name: \u0026#34;ADD DEVICE TYPES TO NETBOX\u0026#34; hosts: localhost connection: local gather_facts: false # No gathering facts about the container execution env tasks: - name: \u0026#34;05 - ADD DEVICE TYPES\u0026#34; netbox.netbox.netbox_device_type: netbox_url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_URL\u0026#39;) }}\u0026#34; netbox_token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; data: model: \u0026#34;{{ item[\u0026#39;model\u0026#39;] }}\u0026#34; manufacturer: \u0026#34;{{ item[\u0026#39;manufacturer\u0026#39;] }}\u0026#34; slug: \u0026#34;{{ item[\u0026#39;slug\u0026#39;] }}\u0026#34; part_number: \u0026#34;{{ item[\u0026#39;part_number\u0026#39;] }}\u0026#34; loop: \u0026#34;{{ device_types }}\u0026#34; Example - Execution #This execution shows that all of the device types are added.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 josh-v@d27199d82bfc:~$ ansible-playbook add_devices.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; PLAY [ADD DEVICE TYPES TO NETBOX] ******************************************************************************************************************** TASK [05 - ADD DEVICE TYPES] ************************************************************************************************************************* changed: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;ASAv\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;asav\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;asav\u0026#39;}) changed: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;CSR1000v\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;csr1000v\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;csr1000v\u0026#39;}) changed: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;IOSv\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;iosv\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;iosv\u0026#39;}) changed: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;nxosv\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;nxosv\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;nxosv\u0026#39;}) changed: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;vEOS\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Arista\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;veos\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;veos\u0026#39;}) PLAY RECAP ******************************************************************************************************************************************* localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 The second execution of playbook shows that with these three settings the module is idempotent:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 josh-v@d27199d82bfc:~$ ansible-playbook add_devices.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; PLAY [ADD DEVICE TYPES TO NETBOX] ******************************************************************************************************************** TASK [05 - ADD DEVICE TYPES] ************************************************************************************************************************* ok: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;ASAv\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;asav\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;asav\u0026#39;}) ok: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;CSR1000v\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;csr1000v\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;csr1000v\u0026#39;}) ok: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;IOSv\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;iosv\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;iosv\u0026#39;}) ok: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;nxosv\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;nxosv\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;nxosv\u0026#39;}) ok: [localhost] =\u0026gt; (item={\u0026#39;model\u0026#39;: \u0026#39;vEOS\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Arista\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;veos\u0026#39;, \u0026#39;part_number\u0026#39;: \u0026#39;veos\u0026#39;}) PLAY RECAP ******************************************************************************************************************************************* localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 After completion of this you will have the device types (hardware models) available for you to assign to devices (coming up next).\nSummary #Device types are important so you know what model of devices you have to work with. This will come in handy as well in your automations that if you have a particular device type that you need to do something against. Such as having a separate type for Cisco Catalyst 3750G vs Catalyst 3750X. They are all 3750 switches, however you may need to apply a unique configuration set against a particular device type. By having this predefined in your source of truth, you are all set to be able to run automations against each.\nHope this has helped. If so, let me know with a comment below or give a thumbs up on the post.\n","date":"2021-01-31","permalink":"https://josh-v.com/netbox-ansible-device-types/","section":"Posts","summary":"\u003cp\u003eA device type is the next piece in the NetBox Device onboarding requirements. The device type corresponds to the model number of the hardware (or virtual machine). This is where you are able to template out devices during their creation. So if you have a console port on a device type, that console port will be created when you create the device. However, \u003cstrong\u003ethere is NOT\u003c/strong\u003e a relationship built between the device type and the device. If the device type gets updated after the device is created, the device itself is \u003cstrong\u003enot\u003c/strong\u003e updated.\u003c/p\u003e","title":"NetBox Ansible Collection: Device Types"},{"content":"Adding your manufacturers via code is the easy way to get started with your NetBox devices. Immediately after adding Sites, the next thing to get going when using NetBox as your Source of Truth is to add in Manufacturers. These are just that, who makes the gear that you use. For this demonstration you will see adding just a few manufacturers. I\u0026rsquo;m not necessarily picking on any vendors and who should or shouldn\u0026rsquo;t be here. It is just what my background brings.\nThis post was created when NetBox was an open source project used often in my automation framework. I have moved on to using Nautobot due to the project vision and providing a methodology that will drive network automation forward further. You may want to take a look at it yourself. Module Documentation # Read the Docs GitHub This module does require pynetbox to execute properly\nEnvironment #For this demo, here are the versions shown:\nComponent Version NetBox v2.9.9 (NetBox Docker) NetBox Ansible Collection v1.1.0 pynetbox 5.1.0 Data File #The documentation indicates that there are two parameters, name and slug. I\u0026rsquo;m not going to modify the slug in any way for these as the auto-generated slug is just fine. Because of this, the demo will not have a more complex variable, just a list of manufacturers.\n--- manufacturers: - Arista - Cisco - Juniper Example #Example - Adding Devices #Getting started I already have a Cisco manufacturer included from a different demo. This will not hurt what is being demonstrated here. The task to add a manufacturer looks like:\n--- - name: \u0026#34;ADD MANUFACTURERS TO NETBOX\u0026#34; hosts: localhost connection: local gather_facts: false # No gathering facts about the container execution env tasks: - name: \u0026#34;05 - ADD MANUFACTURERS\u0026#34; # Already present, showing idempotency netbox.netbox.netbox_manufacturer: netbox_url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_URL\u0026#39;) }}\u0026#34; netbox_token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; data: name: \u0026#34;{{ item }}\u0026#34; loop: \u0026#34;{{ manufacturers }}\u0026#34; Here is the before:\nExample - Execution #Pretty short and sweet on this playbook. With having Cisco already present, you can see that the module is idempotent:\n1 2 3 4 5 6 7 8 9 10 11 12 13 josh-v@d27199d82bfc:~$ ansible-playbook add_manufacturers.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; PLAY [ADD MANUFACTURERS TO NETBOX] ******************************************************************************************************* TASK [05 - ADD MANUFACTURERS] ************************************************************************************************************ changed: [localhost] =\u0026gt; (item=Arista) ok: [localhost] =\u0026gt; (item=Cisco) changed: [localhost] =\u0026gt; (item=Juniper) PLAY RECAP ******************************************************************************************************************************* localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Now you see all of them showing up in NetBox.\nSummary #The manufacturers are a base to adding devices. To add/sync manufacturers you can leverage the NetBox Ansible Collection for manufacturers to get this data synced. To have your entire environment completely automated with using Ansible, this is a great solution.\n","date":"2020-12-20","permalink":"https://josh-v.com/netbox-ansible-manufacturers/","section":"Posts","summary":"\u003cp\u003eAdding your manufacturers via code is the easy way to get started with your NetBox devices. Immediately after adding Sites, the next thing to get going when using NetBox as your Source of Truth is to add in Manufacturers. These are just that, who makes the gear that you use. For this demonstration you will see adding just a few manufacturers. I\u0026rsquo;m not necessarily picking on any vendors and who should or shouldn\u0026rsquo;t be here. It is just what my background brings.\u003c/p\u003e","title":"NetBox Ansible Collection: Manufacturers"},{"content":"Platforms are an optional item when adding devices into NetBox. The platform is the OS that you are going to be using. Most often this is used to help identify which driver your automation platform is going to be using. Specifically the slug of the platform is what needs to match. So in the terms of Ansible (since we are using Ansible to populate NetBox), you will want to set Cisco IOS devices to ios. By having the slug match the automation platform name you have that information in your inventory. For these reasons I strongly recommend setting the Platform for devices.\nThis post was created when NetBox was an open source project used often in my automation framework. I have moved on to using Nautobot due to the project vision and providing a methodology that will drive network automation forward further. You may want to take a look at it yourself. Module Documentation # Read the Docs GitHub This module does require pynetbox to execute properly\nEnvironment #For this demo, here are the versions shown:\nComponent Version NetBox v2.9.9 (NetBox Docker) NetBox Ansible Collection v1.1.0 pynetbox 5.1.0 Data File #Now that you may want to have a different slug than what is displayed, the data structure is getting slightly more complex than the manufacturers file. There will be a list of dictionaries, where the dictionary has three keys: name, slug, and manufacturer.\n--- platforms: - name: Arista EOS slug: eos manufacturer: Arista - name: Cisco IOS slug: ios manufacturer: Cisco - name: JUNOS slug: junos manufacturer: Juniper Example #Example - Adding Devices #Getting started I already have a Cisco manufacturer included from a different demo. This will not hurt what is being demonstrated here. The task to add a manufacturer looks like:\n--- - name: \u0026#34;ADD PLATFORMS TO NETBOX\u0026#34; hosts: localhost connection: local gather_facts: false # No gathering facts about the container execution env tasks: - name: \u0026#34;05 - ADD PLATFORMS\u0026#34; netbox.netbox.netbox_platform: netbox_url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_URL\u0026#39;) }}\u0026#34; netbox_token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; data: name: \u0026#34;{{ item[\u0026#39;name\u0026#39;] }}\u0026#34; loop: \u0026#34;{{ platforms }}\u0026#34; Example - Execution #This execution shows that all of the platforms are added.\n1 2 3 4 5 6 7 8 9 10 11 12 13 josh-v@d27199d82bfc:~$ ansible-playbook add_platforms.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; PLAY [ADD PLATFORMS TO NETBOX] *********************************************************************************************************************** TASK [05 - ADD PLATFORMS] **************************************************************************************************************************** changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Arista EOS\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;eos\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Arista\u0026#39;}) changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Cisco IOS\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;ios\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;}) changed: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;JUNOS\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;junos\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Juniper\u0026#39;}) PLAY RECAP ******************************************************************************************************************************************* localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 The second execution of playbook shows that with these three settings the module is idempotent:\n1 2 3 4 5 6 7 8 9 10 11 12 13 josh-v@d27199d82bfc:~$ ansible-playbook add_platforms.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; PLAY [ADD PLATFORMS TO NETBOX] *********************************************************************************************************************** TASK [05 - ADD PLATFORMS] **************************************************************************************************************************** ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Arista EOS\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;eos\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Arista\u0026#39;}) ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Cisco IOS\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;ios\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Cisco\u0026#39;}) ok: [localhost] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;JUNOS\u0026#39;, \u0026#39;slug\u0026#39;: \u0026#39;junos\u0026#39;, \u0026#39;manufacturer\u0026#39;: \u0026#39;Juniper\u0026#39;}) PLAY RECAP ******************************************************************************************************************************************* localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Now you see all of them showing up in NetBox.\nWhen editing the Cisco platform you see the result visually.\nSummary #Platforms are one of the items that you will strongly want to get updated into NetBox. By associating a device with a platform you can then use it in the inventory plugins to identify things such as the ansible_network_os dynamically. Need to have a new platform to test things with, just create a new platform, change a few settings, and the information is dynamically available within your playbooks.\nHope this has helped. If so, let me know with a comment below or give a thumbs up on the post.\n","date":"2020-12-20","permalink":"https://josh-v.com/netbox-ansible-platforms/","section":"Posts","summary":"\u003cp\u003ePlatforms are an optional item when adding devices into NetBox. The platform is the OS that you are going to be using. Most often this is used to help identify which driver your automation platform is going to be using. Specifically the slug of the platform is what needs to match. So in the terms of Ansible (since we are using Ansible to populate NetBox), you will want to set Cisco IOS devices to \u003cstrong\u003eios\u003c/strong\u003e. By having the slug match the automation platform name you have that information in your inventory. For these reasons I strongly recommend setting the Platform for devices.\u003c/p\u003e","title":"NetBox Ansible Collection: Platforms"},{"content":"This post dives into the NetBox Ansible Content Collection module to create/update a Site. As I start into this series on looking at the modules that create/update/delete data from NetBox, the question that I keep asking myself is should I be looking at the modules that are creating/updating/deleting items? The reason that I ask this to myself is because I am a firm believer that automation should be coming from NetBox as its Source of Truth (SoT). You can hear/read plenty more about these thoughts on posts and videos here:\nMinneapolis Ansible Meetup April 2020 Talk Ansible Guest Blog Post This post was created when NetBox was an open source project used often in my automation framework. I have moved on to using Nautobot due to the project vision and providing a methodology that will drive network automation forward further. You may want to take a look at it yourself. When it comes to creating and deleting sites in NetBox, this one is an easy one. In my opinion this is a yes it should be. Most likely an IT tool is not the tool that will be the Source of Truth as it comes to physical sites. So this module in particualr that should be looked at and put into production use with Ansible.\nEnvironment #For this demo, here are the versions shown:\nComponent Version NetBox v2.9.9 (NetBox Docker) NetBox Ansible Collection v1.1.0 pynetbox 5.1.0 Site Module #Within NetBox, the site is the most basic unit, and is required for devices to be added. This is the first thing that you should do when creating a NetBox instance is to start to build out sites. There are a many set of parameters that you can add to your sites, but the minimum required are:\nname: The name of the site Take a look at the documentation for all of the additional parameters. The ones that stick out to me (and there are many more) include:\nasn: The BGP AS Number contact name \u0026amp; email: Site contact information physical and shipping addresses tags time_zone Examples #The point of these posts are to show examples and get you started. So let\u0026rsquo;s get started. At the beginning of this there are going to be four sites that we can check out with the query function:\n1 2 3 4 5 6 7 TASK [05 - QUERY SITES] ********************************************************************************************************************************************** ansible_facts: site_list_before: - DEN - MSP - NYC - PDX The data here instead of coming from a system of record that has sites will come from a YAML file. So the first step would be to look at getting data from a data source that has sites. This could be a CRM tool if you were a MSP, or any other tooling that has your sites. Here is what the data would look like:\n--- sites: - name: MSP time_zone: America/Chicago status: active description: Minneapolis - name: DEN time_zone: America/Denver status: active description: Denver - name: NYC time_zone: America/New_York status: active description: New York - name: PDX time_zone: America/Los_Angeles status: active description: Portland Running the following playbook multiple times will show that the module itself is idempotent in that it will not keep creating sites.\n--- - name: \u0026#34;SETUP SITES\u0026#34; hosts: localhost connection: local gather_facts: no tasks: - name: \u0026#34;05 - QUERY SITES\u0026#34; set_fact: site_list_before: \u0026#34;{{ query(\u0026#39;netbox.netbox.nb_lookup\u0026#39;, \u0026#39;sites\u0026#39;) | json_query(\u0026#39;[*].value.name\u0026#39;) }}\u0026#34; - name: \u0026#34;10 - SETUP SITES\u0026#34; netbox.netbox.netbox_site: netbox_url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_URL\u0026#39;) }}\u0026#34; netbox_token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; data: \u0026#34;{{ site }}\u0026#34; state: present validate_certs: False loop: \u0026#34;{{ sites }}\u0026#34; loop_control: loop_var: site label: \u0026#34;{{ site[\u0026#39;name\u0026#39;] }}\u0026#34; The output below is from a second run. The sites for the current NetBox demo was originally deployed with this.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 TASK [05 - QUERY SITES] ********************************************************************************************** ok: [localhost] =\u0026gt; changed=false ansible_facts: site_list_before: - DEN - MSP - NYC - PDX TASK [10 - SETUP SITES] ********************************************************************************************** ok: [localhost] =\u0026gt; (item=MSP) =\u0026gt; changed=false ansible_loop_var: site msg: site MSP already exists site: description: Minneapolis name: MSP status: active time_zone: America/Chicago ok: [localhost] =\u0026gt; (item=DEN) =\u0026gt; changed=false ansible_loop_var: site msg: site DEN already exists site: description: Denver name: DEN status: active time_zone: America/Denver ok: [localhost] =\u0026gt; (item=NYC) =\u0026gt; changed=false ansible_loop_var: site msg: site NYC already exists site: description: New York name: NYC status: active time_zone: America/New_York ok: [localhost] =\u0026gt; (item=PDX) =\u0026gt; changed=false ansible_loop_var: site msg: site PDX already exists site: description: Portland name: PDX status: active time_zone: America/Los_Angeles PLAY RECAP *********************************************************************************************************** localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 In here we see that there were two tasks that showed ok and no tasks in the other sections of the play recap.\nAdding an additional site #The source for the sites just had a new site added. This is adding the Orlando location. As such the data now looks like this:\n--- sites: - name: MSP time_zone: America/Chicago status: active description: Minneapolis - name: DEN time_zone: America/Denver status: active description: Denver - name: NYC time_zone: America/New_York status: active description: New York - name: PDX time_zone: America/Los_Angeles status: active description: Portland - name: MCO time_zone: America/New_York status: active description: Orlando With the new location, the Ansible Playbook is executed and we see a new site is added:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 TASK [05 - QUERY SITES] ********************************************************************************************** ok: [localhost] =\u0026gt; changed=false ansible_facts: site_list_before: - DEN - MSP - NYC - PDX TASK [10 - SETUP SITES] ********************************************************************************************** ok: [localhost] =\u0026gt; (item=MSP) =\u0026gt; changed=false ansible_loop_var: site msg: site MSP already exists site: description: Minneapolis name: MSP status: active time_zone: America/Chicago ok: [localhost] =\u0026gt; (item=DEN) =\u0026gt; changed=false ansible_loop_var: site msg: site DEN already exists site: description: Denver name: DEN status: active time_zone: America/Denver ok: [localhost] =\u0026gt; (item=NYC) =\u0026gt; changed=false ansible_loop_var: site msg: site NYC already exists site: description: New York name: NYC status: active time_zone: America/New_York ok: [localhost] =\u0026gt; (item=PDX) =\u0026gt; changed=false ansible_loop_var: site msg: site PDX already exists site: description: Portland name: PDX status: active time_zone: America/Los_Angeles changed: [localhost] =\u0026gt; (item=MCO) =\u0026gt; changed=true ansible_loop_var: site msg: site MCO created site: description: Orlando name: MCO status: active time_zone: America/New_York PLAY RECAP *********************************************************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Taking a look at line 45 in this last execution you see the message site MCO created. This shows that it as created and there was a task that showed chagned in the play recap.\nRemoving Sites #In this example for the removing of sites I am going to keep it a little bit more manual. I\u0026rsquo;m going to create a new variable in the group_vars/all/sites.yml file called closed_sites. So in this scenario the Orlando site was opened, but very quickly it was decided to close it down. So now we need to remove the site from NetBox. The group_vars/all/sites.yml now looks like below:\n--- sites: - name: MSP time_zone: America/Chicago status: active description: Minneapolis - name: DEN time_zone: America/Denver status: active description: Denver - name: NYC time_zone: America/New_York status: active description: New York - name: PDX time_zone: America/Los_Angeles status: active description: Portland closed_sites: - name: MCO time_zone: America/New_York status: active description: Orlando The updated Ansible Playbook now needs to remove any sites that are showing up in the closed sites:\n--- - name: \u0026#34;SETUP SITES\u0026#34; hosts: localhost connection: local gather_facts: no tasks: - name: \u0026#34;05 - QUERY SITES\u0026#34; set_fact: site_list_before: \u0026#34;{{ query(\u0026#39;netbox.netbox.nb_lookup\u0026#39;, \u0026#39;sites\u0026#39;) | json_query(\u0026#39;[*].value.name\u0026#39;) }}\u0026#34; - name: \u0026#34;10 - SETUP SITES\u0026#34; netbox.netbox.netbox_site: netbox_url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_URL\u0026#39;) }}\u0026#34; netbox_token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; data: \u0026#34;{{ site }}\u0026#34; state: present validate_certs: False loop: \u0026#34;{{ sites }}\u0026#34; loop_control: loop_var: site label: \u0026#34;{{ site[\u0026#39;name\u0026#39;] }}\u0026#34; - name: \u0026#34;20 - REMOVE CLOSED SITES\u0026#34; netbox.netbox.netbox_site: netbox_url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_URL\u0026#39;) }}\u0026#34; netbox_token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; data: \u0026#34;{{ site }}\u0026#34; state: absent validate_certs: False loop: \u0026#34;{{ sites }}\u0026#34; loop_control: loop_var: site label: \u0026#34;{{ site[\u0026#39;name\u0026#39;] }}\u0026#34; - name: \u0026#34;25 - QUERY SITES AT END\u0026#34; set_fact: site_list_end: \u0026#34;{{ query(\u0026#39;netbox.netbox.nb_lookup\u0026#39;, \u0026#39;sites\u0026#39;) | json_query(\u0026#39;[*].value.name\u0026#39;) }}\u0026#34; - name: \u0026#34;30 - SHOW RESULTS\u0026#34; debug: msg: - \u0026#34;{{ site_list_before }}\u0026#34; - \u0026#34;{{ site_list_end }}\u0026#34; The result of the playbook shows that we had the site at the beginning, then we were able to successfully remove it in Task 20 to remove the closed sites.\nWhen removing a site, you do need to make sure that all of the corresponding devices and other relationships are gone from the site. NetBox will not allow you to remove a site without it being empty first.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 TASK [05 - QUERY SITES] ********************************************************************************************** task path: /local/add_sites.yml:7 ok: [localhost] =\u0026gt; changed=false ansible_facts: site_list_before: - DEN - MCO - MSP - NYC - PDX TASK [10 - SETUP SITES] ********************************************************************************************** task path: /local/add_sites.yml:11 ok: [localhost] =\u0026gt; (item=MSP) =\u0026gt; changed=false ansible_loop_var: site msg: site MSP already exists site: description: Minneapolis name: MSP status: active time_zone: America/Chicago ok: [localhost] =\u0026gt; (item=DEN) =\u0026gt; changed=false ansible_loop_var: site msg: site DEN already exists site: description: Denver name: DEN status: active time_zone: America/Denver ok: [localhost] =\u0026gt; (item=NYC) =\u0026gt; changed=false ansible_loop_var: site msg: site NYC already exists site: description: New York name: NYC status: active time_zone: America/New_York ok: [localhost] =\u0026gt; (item=PDX) =\u0026gt; changed=false ansible_loop_var: site msg: site PDX already exists site: description: Portland name: PDX status: active time_zone: America/Los_Angeles TASK [20 - REMOVE CLOSED SITES] ************************************************************************************** task path: /local/add_sites.yml:23 changed: [localhost] =\u0026gt; (item=MCO) =\u0026gt; changed=true ansible_loop_var: site msg: site MCO deleted site: description: Orlando name: MCO status: active time_zone: America/New_York TASK [25 - QUERY SITES AT END] *************************************************************************************** task path: /local/add_sites.yml:35 ok: [localhost] =\u0026gt; changed=false ansible_facts: site_list_end: - DEN - MSP - NYC - PDX TASK [30 - SHOW RESULTS] ********************************************************************************************* task path: /local/add_sites.yml:39 ok: [localhost] =\u0026gt; msg: - - DEN - MCO - MSP - NYC - PDX - - DEN - MSP - NYC - PDX META: ran handlers META: ran handlers PLAY RECAP *********************************************************************************************************** localhost : ok=5 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Summary #This module is a very good module with a lot of options to get you started. This is absolutely a module that I would become familiar with as your organization is changing over time. This will allow you to keep your NetBox environment up to date with the site changes as you get new and closed sites alike. Hopefully this has been helpful to demonstrate it\u0026rsquo;s capabities. Let me know your comments below, or give it a thumbs up if you have found this helpful.\nThanks,\nJosh\n","date":"2020-12-13","permalink":"https://josh-v.com/netbox-ansible-sites/","section":"Posts","summary":"\u003cp\u003eThis post dives into the \u003ca href=\"https://netbox-ansible-collection.readthedocs.io/en/latest/\" target=\"_blank\" rel=\"noreferrer\"\u003eNetBox Ansible Content Collection\u003c/a\u003e module to create/update a \u003ca href=\"https://netbox-ansible-collection.readthedocs.io/en/latest/plugins/modules/netbox_site/netbox.netbox.netbox_site_module.html\" target=\"_blank\" rel=\"noreferrer\"\u003eSite\u003c/a\u003e. As I start into this series on looking at the modules that create/update/delete data from NetBox, the question that I keep asking myself is should I be looking at the modules that are creating/updating/deleting items? The reason that I ask this to myself is because I am a firm believer that automation should be coming from NetBox as its Source of Truth (SoT). You can hear/read plenty more about these thoughts on posts and videos here:\u003c/p\u003e","title":"NetBox Ansible Collection: Site Module"},{"content":"The NetBox lookup plugin is to get information out of NetBox for use within Ansible. This uses pynetbox to query the NetBox API for the information requested. On top of being helpful in gathering data from NetBox (when it is not your inventory source), but it is extremely helpful in larger NetBox deployments when compared to using the URI module as well. If you wish to use NetBox as your inventory source, you should definitely read my previous post on getting started with the NetBox Inventory Plugin.\nRead the Docs GitHub Source File Installing the Colleciton The recommended Jinja function to use with this lookup plugin is the query function. This tells Ansible that the result of the lookup should be a type list. The same behavior is also available by using the lookup function, in conjunction with the parameter wantlist=true. For this post we will use the query method.\nThis post was created when NetBox was an open source project used often in my automation framework. I have moved on to using Nautobot due to the project vision and providing a methodology that will drive network automation forward further. You may want to take a look at it yourself. Methodology #My methodology for gathering this information is that the plugin is looking to get the Django application (Sites, Devices, IPAM)\nEnvironment #For this demo, here are the versions shown:\nComponent Version NetBox v2.9.9 (NetBox Docker) NetBox Ansible Collection v1.1.0 pynetbox 5.1.0 Query Plugin Note #So I was originally trying to use the Jinja variable template when I was working with the plugin. The query function does not need the templating language (wrapped in {{ }}). The variable name should be used without the wrapping and just work.\nParameter: Terms #The parameter _terms as I can understand relates to which end point within the pynetbox endpoint is being referenced. Digging into the code itself, I have found that the following is the available endpoints:\nLookup Endpoint Corresponding pynetbox endpoint aggregates netbox.ipam.aggregates circuit-terminations netbox.circuits.circuit_terminations circuit-types netbox.circuits.circuit_types circuits netbox.circuits.circuits circuit-providers netbox.circuits.providers cables netbox.dcim.cables cluster-groups netbox.virtualization.cluster_groups cluster-types netbox.virtualization.cluster_types clusters netbox.virtualization.clusters config-contexts netbox.extras.config_contexts console-connections netbox.dcim.console_connections console-ports netbox.dcim.console_ports console-server-port-templates\u0026quot; netbox.dcim.console_server_port_templates console-server-ports netbox.dcim.console_server_ports device-bay-templates netbox.dcim.device_bay_templates device-bays netbox.dcim.device_bays device-roles netbox.dcim.device_roles device-types netbox.dcim.device_types devices netbox.dcim.devices export-templates netbox.dcim.export_templates front-port-templates netbox.dcim.front_port_templates front-ports netbox.dcim.front_ports graphs netbox.extras.graphs image-attachments netbox.extras.image_attachments interface-connections netbox.dcim.interface_connections interface-templates netbox.dcim.interface_templates interfaces netbox.dcim.interfaces inventory-items netbox.dcim.inventory_items ip-addresses netbox.ipam.ip_addresses manufacturers netbox.dcim.manufacturers object-changes netbox.extras.object_changes platforms netbox.dcim.platforms power-connections netbox.dcim.power_connections power-outlet-templates netbox.dcim.power_outlet_templates power-outlets netbox.dcim.power_outlets power-port-templates netbox.dcim.power_port_templates power-ports netbox.dcim.power_ports prefixes netbox.ipam.prefixes rack-groups netbox.dcim.rack_groups rack-reservations netbox.dcim.rack_reservations rack-roles netbox.dcim.rack_roles racks netbox.dcim.racks rear-port-templates netbox.dcim.rear_port_templates rear-ports netbox.dcim.rear_ports regions netbox.dcim.regions reports netbox.extras.reports rirs netbox.ipam.rirs roles netbox.ipam.roles secret-roles netbox.secrets.secret_roles secrets netbox.secrets.secrets services netbox.ipam.services sites netbox.dcim.sites tags netbox.extras.tags tenant-groups netbox.tenancy.tenant_groups tenants netbox.tenancy.tenants topology-maps netbox.extras.topology_maps virtual-chassis netbox.dcim.virtual_chassis virtual-machines netbox.virtualization.virtual_machines virtualization-interfaces netbox.virtualization.interfaces vlan-groups netbox.ipam.vlan_groups vlans netbox.ipam.vlans vrfs netbox.ipam.vrfs Examples #The goal is to really dig into the examples to see how it is used. Let\u0026rsquo;s get right to it. For these demos I am using the following definitions for the Play:\n--- - name: \u0026#34;GATHER DATA FROM NETBOX\u0026#34; hosts: localhost connection: local gather_facts: no vars: netbox_url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_API\u0026#39;) }}\u0026#34; netbox_token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; Of note, the URL and token are embedded into the environment. To maintain consistency with the NetBox Inventory plugin I am mapping NETBOX_API to be the netbox_url. At this moment of writing the lookup plugin does not natively lookup the environment like the other modules do. I do intended to submit a PR to update this.\nGathering Sites #When taking a look at gathering sites the following task is used:\n- name: \u0026#34;TASK 1: GET SITES WITH NB_QUERY\u0026#34; set_fact: sites: \u0026#34;{{ query(\u0026#39;netbox.netbox.nb_lookup\u0026#39;, \u0026#39;sites\u0026#39;, api_endpoint=netbox_url, token=netbox_token) }}\u0026#34; - name: \u0026#34;TASK 2: PRINT JUST THE SITE NAMES\u0026#34; debug: msg: \u0026#34;{{ sites | json_query(\u0026#39;[*].value.name\u0026#39;) }}\u0026#34; Gathering Sites: TASK 1 Output #Lines 1-3 are the collecting of data from NetBox itself. In my NetBox demo environment I currently have 4 sites. Instead of giving you the entire output that is quite long, below is that output. Note that there is a bunch of information available to you about each site here.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 - key: 6 value: asn: null circuit_count: null comments: \u0026#39;\u0026#39; contact_email: \u0026#39;\u0026#39; contact_name: \u0026#39;\u0026#39; contact_phone: \u0026#39;\u0026#39; created: \u0026#39;2020-12-06\u0026#39; custom_fields: {} description: Portland device_count: 3 facility: \u0026#39;\u0026#39; id: 6 last_updated: \u0026#39;2020-12-06T15:29:45.112426Z\u0026#39; latitude: null longitude: null name: PDX physical_address: \u0026#39;\u0026#39; prefix_count: null rack_count: null region: null shipping_address: \u0026#39;\u0026#39; slug: pdx status: label: Active value: active tags: [] tenant: null time_zone: America/Los_Angeles url: http://netbox-demo/api/dcim/sites/6/ virtualmachine_count: null vlan_count: null Gathering Sites: TASK 2 Output - Getting Just the Site Names #I used json_query (which uses JMESPATH) to get just the site names. I see a future post on this coming in the future. The result of this gives me the output of just the four sites and not the rest of the data:\n1 2 3 4 5 6 ok: [localhost] =\u0026gt; msg: - DEN - MSP - NYC - PDX Get Devices - Filtered to a single site #You can then filter with the lookup plugin as well. In these two tasks I\u0026rsquo;m going to filter and get the devices that are located at the DEN site. Task 3 you add the api_filter to the plugin definition on the set_fact. The API filters are key/value and are separated with a space inside of the string to have multiple searches. If you wish to search multiple sites or multiple roles (or multiple anything) then you add a second instance of it. The value of the key/value pair is the corresponding slug associated with the search.\n- name: \u0026#34;TASK 3: GET DEVICES WITH ROLE ROUTER AT DEN SITE\u0026#34; set_fact: den_devices: \u0026#34;{{ query(\u0026#39;netbox.netbox.nb_lookup\u0026#39;, \u0026#39;devices\u0026#39;, api_filter=\u0026#39;site=den role=router\u0026#39;, api_endpoint=netbox_url, token=netbox_token) }}\u0026#34; - name: \u0026#34;TASK 4: PRINT THE DEVICES\u0026#34; debug: msg: \u0026#34;{{ den_devices | json_query(\u0026#39;[*].value.name\u0026#39;) }}\u0026#34; Getting Denver Routers Output #The output from these two tasks where there is a single router device at the site Denver is then the following output with the single device on line 77.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 TASK [TASK 3: GET ROLE ROUTERS AT DEN SITE] ************************************************************************************************************************** ansible_facts: den_devices: - key: 4 value: asset_tag: null cluster: null comments: \u0026#39;\u0026#39; config_context: {} created: \u0026#39;2020-12-06\u0026#39; custom_fields: {} device_role: id: 3 name: Router slug: router url: http://netbox-demo/api/dcim/device-roles/3/ device_type: display_name: Cisco IOSV id: 3 manufacturer: id: 1 name: Cisco slug: cisco url: http://netbox-demo/api/dcim/manufacturers/1/ model: IOSV slug: iosv url: http://netbox-demo/api/dcim/device-types/3/ display_name: den-wan01 face: null id: 4 last_updated: \u0026#39;2020-12-12T19:55:16.432666Z\u0026#39; local_context_data: null name: den-wan01 parent_device: null platform: id: 2 name: cisco_ios slug: ios url: http://netbox-demo/api/dcim/platforms/2/ position: null primary_ip: address: 10.16.0.2/24 family: 4 id: 4 url: http://netbox-demo/api/ipam/ip-addresses/4/ primary_ip4: address: 10.16.0.2/24 family: 4 id: 4 url: http://netbox-demo/api/ipam/ip-addresses/4/ primary_ip6: null rack: null serial: 90Q1VEN47MPBMU2718KJ1 site: id: 4 name: DEN slug: den url: http://netbox-demo/api/dcim/sites/4/ status: label: Active value: active tags: - color: 3f51b5 id: 2 name: snmp_monitoring slug: snmp_monitoring url: http://netbox-demo/api/extras/tags/2/ tenant: null url: http://netbox-demo/api/dcim/devices/4/ vc_position: null vc_priority: null virtual_chassis: null TASK [TASK 4: PRINT THE DEVICES OF TYPE ROUTER AT DENVER LOCATION] *************************************************************************************************** ok: [localhost] =\u0026gt; msg: - den-wan01 Searching Multiple Locations #With this you are able to filter many things. To filter multiple sites, say you wanted to get the devices at both of the sites DEN and MSP. To do this you change the filter to be site=den site=-msp. Seeing this you get the following:\nI am only showing a single device corresponding to MSP \u0026amp; DEN site.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 ansible_facts: msp_den_devices: - key: 5 value: asset_tag: null cluster: null comments: \u0026#39;\u0026#39; config_context: {} created: \u0026#39;2020-12-06\u0026#39; custom_fields: {} device_role: id: 2 name: Network slug: network url: http://netbox-demo/api/dcim/device-roles/2/ device_type: display_name: Cisco IOSV id: 3 manufacturer: id: 1 name: Cisco slug: cisco url: http://netbox-demo/api/dcim/manufacturers/1/ model: IOSV slug: iosv url: http://netbox-demo/api/dcim/device-types/3/ display_name: den-dist01 face: null id: 5 last_updated: \u0026#39;2020-12-06T17:16:02.266041Z\u0026#39; local_context_data: null name: den-dist01 parent_device: null platform: id: 2 name: cisco_ios slug: ios url: http://netbox-demo/api/dcim/platforms/2/ position: null primary_ip: address: 10.17.1.2/30 family: 4 id: 5 url: http://netbox-demo/api/ipam/ip-addresses/5/ primary_ip4: address: 10.17.1.2/30 family: 4 id: 5 url: http://netbox-demo/api/ipam/ip-addresses/5/ primary_ip6: null rack: null serial: 9ZYX8XZUMP0AF69YGO5Z5 site: id: 4 name: DEN slug: den url: http://netbox-demo/api/dcim/sites/4/ status: label: Active value: active tags: - color: 3f51b5 id: 2 name: snmp_monitoring slug: snmp_monitoring url: http://netbox-demo/api/extras/tags/2/ tenant: null url: http://netbox-demo/api/dcim/devices/5/ vc_position: null vc_priority: null virtual_chassis: null Here is the result of Task 6, which is the list of the devices:\n1 2 3 4 5 6 7 8 9 TASK [TASK 6: PRINT THE DEVICES AT DEN \u0026amp; MSP LOCATIONS] ************************************************************************************************************** ok: [localhost] =\u0026gt; msg: - den-dist01 - den-dist02 - den-wan01 - msp-dist01 - msp-dist02 - msp-wan01 Working With Large Data Sets #One may say that I can just get the data from using the URI module from Ansible. I\u0026rsquo;ve been there as well on this. One of the larger draws of using the lookup plugin is the ability to handle pagination of the results natively. Consider the following task, where I have changed the MAX_PAGE_SIZE environment variable to 2 in order to demonstrate the paging setup.\n- name: \u0026#34;TASK 7: GET DATA FROM NETBOX VIA THE REST API\u0026#34; uri: url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_URL\u0026#39;) }}/api/dcim/devices/?site=den\u0026amp;limit=2\u0026#34; method: \u0026#34;GET\u0026#34; headers: Content-Type: \u0026#34;application/json\u0026#34; Authorization: \u0026#34;token {{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; status_code: 200 register: search_result - name: \u0026#34;TASK 8: PRINT LENGTH OF PAGED SETUP\u0026#34; debug: msg: - \u0026#34;Length of result on paginated response: {{ search_result[\u0026#39;json\u0026#39;][\u0026#39;results\u0026#39;] | length }}\u0026#34; - \u0026#34;Total results (if no paging): {{ search_result[\u0026#39;json\u0026#39;][\u0026#39;count\u0026#39;] }}\u0026#34; Task 7 gets the data, and looking at the response data coming back we can see that there is a second page by the next field:\n1 2 3 4 5 json: count: 3 next: http://netbox-demo/api/dcim/devices/?limit=2\u0026amp;offset=2\u0026amp;site=den previous: null results: Task 8 then confirms this for us:\n1 2 3 msg: - \u0026#39;Length of result on paginated response: 2\u0026#39; - \u0026#39;Total results (if no paging): 3\u0026#39; This is where leveraging pynetbox under the hood and it handling the pagination will be helpful. Some day there may be an Ansible module that handles API calls and combines the results on multiple responses. But today one would need to add a fair amount of logic handling into a Playbook execution to handle pagination.\nThe result is handling the paging in the task and makes the life very easy to get data from NetBox with it!\n1 2 3 4 5 TASK [TASK 10: PRINT THE DEVICES AT DEN LOCATION] ******************************************************************************************************************** ok: [localhost] =\u0026gt; msg: - den-dist01 - den-dist02 Summary #The lookup plugin from the NetBox Ansible Content Collection is a great tool to help get your NetBox search data into your Ansible Playbooks. You can filter as needed, and you get the data into a format that you can then use!\nFinal Playbook #Since there were a lot of demos in here, below is the final playbook. In the environment are the NETBOX variables that you set the environment variables and you can use this same playbook to get started. Just make the updates as needed!\n--- - name: \u0026#34;GATHER DATA FROM NETBOX\u0026#34; hosts: localhost connection: local gather_facts: no vars: netbox_url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_API\u0026#39;) }}\u0026#34; netbox_token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; tasks: - name: \u0026#34;GET SITES WITH NB_QUERY\u0026#34; set_fact: sites: \u0026#34;{{ query(\u0026#39;netbox.netbox.nb_lookup\u0026#39;, \u0026#39;sites\u0026#39;, api_endpoint=netbox_url, token=netbox_token) }}\u0026#34; - debug: msg: \u0026#34;{{ sites | json_query(\u0026#39;[*].value.name\u0026#39;) }}\u0026#34; - name: \u0026#34;TASK 3: GET ROLE ROUTERS AT DEN SITE\u0026#34; set_fact: den_devices: \u0026#34;{{ query(\u0026#39;netbox.netbox.nb_lookup\u0026#39;, \u0026#39;devices\u0026#39;, api_filter=\u0026#39;site=den role=router\u0026#39;, api_endpoint=netbox_url, token=netbox_token) }}\u0026#34; - name: \u0026#34;TASK 4: PRINT THE DEVICES OF TYPE ROUTER AT DENVER LOCATION\u0026#34; debug: msg: \u0026#34;{{ den_devices | json_query(\u0026#39;[*].value.name\u0026#39;) }}\u0026#34; - name: \u0026#34;TASK 5: GET DEVICES AT DEN \u0026amp; MSP SITES\u0026#34; set_fact: msp_den_devices: \u0026#34;{{ query(\u0026#39;netbox.netbox.nb_lookup\u0026#39;, \u0026#39;devices\u0026#39;, api_filter=\u0026#39;site=den site=msp\u0026#39;, api_endpoint=netbox_url, token=netbox_token) }}\u0026#34; - name: \u0026#34;TASK 6: PRINT THE DEVICES AT DEN \u0026amp; MSP LOCATIONS\u0026#34; debug: msg: \u0026#34;{{ msp_den_devices | json_query(\u0026#39;[*].value.name\u0026#39;) }}\u0026#34; - name: \u0026#34;TASK 7: GET DATA FROM NETBOX VIA THE REST API\u0026#34; uri: url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_URL\u0026#39;) }}/api/dcim/devices/?site=den\u0026#34; method: \u0026#34;GET\u0026#34; headers: Content-Type: \u0026#34;application/json\u0026#34; Authorization: \u0026#34;token {{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; status_code: 200 register: search_result - name: \u0026#34;TASK 8: PRINT LENGTH OF PAGED SETUP\u0026#34; debug: msg: - \u0026#34;Length of result on paginated response: {{ search_result[\u0026#39;json\u0026#39;][\u0026#39;results\u0026#39;] | length }}\u0026#34; - \u0026#34;Total results (if no paging): {{ search_result[\u0026#39;json\u0026#39;][\u0026#39;count\u0026#39;] }}\u0026#34; - name: \u0026#34;TASK 9: GET DEVICES AT DEN SITE\u0026#34; set_fact: den_devices: \u0026#34;{{ query(\u0026#39;netbox.netbox.nb_lookup\u0026#39;, \u0026#39;devices\u0026#39;, api_filter=\u0026#39;site=den\u0026#39;, api_endpoint=netbox_url, token=netbox_token) }}\u0026#34; - name: \u0026#34;TASK 10: PRINT THE DEVICES AT DEN LOCATION\u0026#34; debug: msg: \u0026#34;{{ den_devices | json_query(\u0026#39;[*].value.name\u0026#39;) }}\u0026#34; Let me know your thoughts below! Like it if you have found it valuable.\nJosh\n","date":"2020-12-12","permalink":"https://josh-v.com/netbox-ansible-lookup-plugin/","section":"Posts","summary":"\u003cp\u003eThe NetBox lookup plugin is to \u003cstrong\u003eget information\u003c/strong\u003e out of NetBox for use within Ansible. This uses \u003ca href=\"https://github.com/digitalocean/pynetbox\" target=\"_blank\" rel=\"noreferrer\"\u003epynetbox\u003c/a\u003e to query the NetBox API for the information requested. On top of being helpful in gathering data from NetBox (when it is not your inventory source), but it is extremely helpful in larger NetBox deployments when compared to using the URI module as well. If you wish to use NetBox as your inventory source, you should definitely read my previous post on getting started with the \u003ca href=\"https://josh-v.com/netbox_ansible_collection/netbox-ansible-inventory_plugin/\" target=\"_blank\" rel=\"noreferrer\"\u003eNetBox Inventory Plugin\u003c/a\u003e.\u003c/p\u003e","title":"NetBox Ansible Collection: Lookup Plugin"},{"content":"The documentation can be found on ReadTheDocs. This is going to be starting out with the basics of the plugin and getting some sample output and to show how to form groups to be used.\nThis post was created when NetBox was an open source project used often in my automation framework. I have moved on to using Nautobot due to the project vision and providing a methodology that will drive network automation forward further. You may want to take a look at it yourself. This particular plugin DOES NOT require pynetbox to be used.\nLate addition: You can see a corresponding video on YouTube:\nPurpose #The purpose of the NetBox Inventory plugin is to provide an inventory to use within your Ansible automations. The plugin will gather information from NetBox and from the data create groups and an inventory for use by Ansible.\nInstall #Please checkout the first post in the series for the getting started / installation process.\nEnvironment #For this demo, as there are many pieces of information brought back by the inventory I have reduced the inventory to two hosts.\nHere are the rest of the installation versions:\nComponent Version NetBox v2.9.9 (NetBox Docker) NetBox Ansible Collection v1.1.0 pynetbox Not required - Not installed Basic Setup - No Groups #First a good practice for working on systems is to use environment variables to define the server and secret information within the environment. The environment has been configured with for this demo are:\nEnvironment Variable Name What is in the variable NETBOX_API API URL for NetBox such as http://netbox.example.com NETBOX_TOKEN API Token set inside of NetBox Admin -\u0026gt; Admin -\u0026gt; Tokens From the documentation these variables if not defined in the inventory YAML file that the plugin will search for the values to be in the environment with NETBOX_API and NETBOX_TOKEN. This allows you to share the inventory file to be shared across multiple NetBox environments, all based on the environment.\nYou configure the inventory with a YAML file that references the plugin. Here is the starting point to verify that we can get data from NetBox into an inventory before we add some of the filtering and groupings.\n# netbox_inventory.yml --- plugin: netbox.netbox.nb_inventory validate_certs: false config_context: false What is being done here:\nLine Number Key Value 2 plugin Which inventory plugin to be used, the FQCN of netbox.netbox.nb_inventory 3 validate_certs If TLS is configured, then the certificates will not be validated. 4 config_context This is important on large NetBox environments. Setting config_context to false tells the API to not return the config_context field, reducing the amount of data being returned. Alternatively if you do not define the items in the environment, then you would add the keys into the file, such that it looks like below. The rest of the post will assume that the items are configured in the environment.\n# netbox_inventory.yml --- plugin: netbox.netbox.nb_inventory api_endpoint: http://netbox.example.com token: \u0026lt;API_TOKEN here\u0026gt; validate_certs: false config_context: false Basic Setup Result #When executing the inventory ansible-inventory -i netbox_inventory.yml. This example the file name is netbox_inventory.yml. This is completely arbitrary. You can name the inventory any file you wish. Most likely you should name it something that relates to the inventory you are creating. The result of the inventory gives the following:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 { \u0026#34;_meta\u0026#34;: { \u0026#34;hostvars\u0026#34;: { \u0026#34;dcrtr001\u0026#34;: { \u0026#34;ansible_host\u0026#34;: \u0026#34;192.0.2.10\u0026#34;, \u0026#34;custom_fields\u0026#34;: { \u0026#34;os_version\u0026#34;: null \u0026#34;device_roles\u0026#34;: [ \u0026#34;network\u0026#34; ], \u0026#34;device_types\u0026#34;: [ \u0026#34;iosv\u0026#34; ], \u0026#34;is_virtual\u0026#34;: false, \u0026#34;local_context_data\u0026#34;: [ null ], \u0026#34;manufacturers\u0026#34;: [ \u0026#34;cisco\u0026#34; ], \u0026#34;platforms\u0026#34;: [ \u0026#34;cisco_ios\u0026#34; ], \u0026#34;primary_ip4\u0026#34;: \u0026#34;192.0.2.10\u0026#34;, \u0026#34;regions\u0026#34;: [], \u0026#34;services\u0026#34;: [], \u0026#34;sites\u0026#34;: [ \u0026#34;site01\u0026#34; ], \u0026#34;tags\u0026#34;: [] }, \u0026#34;wanrtr002\u0026#34;: { \u0026#34;ansible_host\u0026#34;: \u0026#34;10.10.0.2\u0026#34;, \u0026#34;custom_fields\u0026#34;: { \u0026#34;os_version\u0026#34;: null }, \u0026#34;device_roles\u0026#34;: [ \u0026#34;network\u0026#34; ], \u0026#34;device_types\u0026#34;: [ \u0026#34;csr1000v\u0026#34; ], \u0026#34;is_virtual\u0026#34;: false, \u0026#34;local_context_data\u0026#34;: [ null ], \u0026#34;manufacturers\u0026#34;: [ \u0026#34;cisco\u0026#34; ], \u0026#34;platforms\u0026#34;: [ \u0026#34;cisco_ios\u0026#34; ], \u0026#34;primary_ip4\u0026#34;: \u0026#34;10.10.0.2\u0026#34;, \u0026#34;regions\u0026#34;: [], \u0026#34;services\u0026#34;: [], \u0026#34;sites\u0026#34;: [ \u0026#34;site01\u0026#34; ], \u0026#34;tags\u0026#34;: [] } } }, \u0026#34;all\u0026#34;: { \u0026#34;children\u0026#34;: [ \u0026#34;ungrouped\u0026#34; ] }, \u0026#34;ungrouped\u0026#34;: { \u0026#34;hosts\u0026#34;: [ \u0026#34;dcrtr001\u0026#34;, \u0026#34;wanrtr002\u0026#34; ] } } In this output, you get several components made available. First take notice on lines 62-74 at the bottom. This gives the groupings that will be made available. Since this is the basic query with no groupings defined, there is a single group of ungrouped. This is a child of the all group. Which allows only hosts defined as all or the specific hostname.\nTaking a look at the hostvars that get assigned (lines 1-62), you get several host variables defined from the API call. Including the primary_ip4 address, device_role, an Ansible Host from the primary_ip4 address, and also the custom fields that are part of the NetBox environment.\nGroupings #Next up is adding some more context and adding groupings. This is done within your inventory YAML file with the key of group_by. There are several choices of what you can group objects by, including sites, tenants, tags, platforms, and many more. Take a look at the documentation reference for all of the options. Taking a look at having a grouping of by device_role, tags, sites, and platform gives the following additional groups:\n# netbox_inventory_group_by.yml --- plugin: netbox.netbox.nb_inventory validate_certs: false config_context: false group_by: - device_roles - platforms - tags - sites This now yields the following:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 { \u0026#34;all\u0026#34;: { \u0026#34;children\u0026#34;: [ \u0026#34;device_roles_network\u0026#34;, \u0026#34;platforms_cisco_ios\u0026#34;, \u0026#34;sites_datacenter01\u0026#34;, \u0026#34;sites_site01\u0026#34;, \u0026#34;tags_virtual\u0026#34;, \u0026#34;ungrouped\u0026#34; ] }, \u0026#34;device_roles_network\u0026#34;: { \u0026#34;hosts\u0026#34;: [ \u0026#34;dcrtr001\u0026#34;, \u0026#34;wanrtr002\u0026#34; ] }, \u0026#34;platforms_cisco_ios\u0026#34;: { \u0026#34;hosts\u0026#34;: [ \u0026#34;dcrtr001\u0026#34;, \u0026#34;wanrtr002\u0026#34; ] }, \u0026#34;sites_datacenter01\u0026#34;: { \u0026#34;hosts\u0026#34;: [ \u0026#34;dcrtr001\u0026#34; ] }, \u0026#34;sites_site01\u0026#34;: { \u0026#34;hosts\u0026#34;: [ \u0026#34;wanrtr002\u0026#34; ] }, \u0026#34;tags_virtual\u0026#34;: { \u0026#34;hosts\u0026#34;: [ \u0026#34;dcrtr001\u0026#34; ] } } This filtered out the hostvars as these are not changing.\nThere are now 5 additional groups showing up in the all group. And each of these have different hosts available for you to use in your playbooks. Now these are making sense and you can now have groupings for each of the items.\nFiltering Devices from NetBox #Next up is how do you filter hosts from the NetBox environment? That is done with query_filters key. From the documentation page:\nList of parameters passed to the query string for both devices and VMs (Multiple values may be separated by commas)\nIn the testing I was not able to get multiple values on a status. This may need some clarification from the project.\nFirst search that was changed is that I moved wanrtr002 to an offline state. Now when running the following inventory with a query filter status of active, the host is no longer in the grouping:\n# netbox_inventory_filtered.yml --- plugin: netbox.netbox.nb_inventory validate_certs: false config_context: false group_by: - device_roles - platforms - tags - sites query_filters: - status: \u0026#34;active\u0026#34; Looking at just the platforms_cisco_ios grouping, there is now a single device.\n1 2 3 4 5 \u0026#34;platforms_cisco_ios\u0026#34;: { \u0026#34;hosts\u0026#34;: [ \u0026#34;dcrtr001\u0026#34; ] }, You can add additional queries to the query_filters key, such that status is listed twice. Once we add the second status then both devices show up again:\nquery_filters: - status: \u0026#34;active\u0026#34; - status: \u0026#34;offline\u0026#34; 1 2 3 4 5 6 \u0026#34;platforms_cisco_ios\u0026#34;: { \u0026#34;hosts\u0026#34;: [ \u0026#34;dcrtr001\u0026#34;, \u0026#34;wanrtr002\u0026#34; ] }, Adding Variables #The method to add custom hostvars to your inventory from NetBox, is done with the compose parameter. From the documentation site:\nList of custom ansible host vars to create from the device object fetched from NetBox\nTHe first item that I use often with the compose parameter of the plugin is to set the ansible_network_os for a device. This is very helpful with a multi-vendor environment that allows you to run tasks against different network OS\u0026rsquo;s. The final demonstration inventory YAML file is this:\n# netbox_inventory_all.yml --- plugin: netbox.netbox.nb_inventory validate_certs: false config_context: false group_by: - device_roles - platforms - tags - sites query_filters: - status: \u0026#34;active\u0026#34; - status: \u0026#34;offline\u0026#34; compose: ansible_network_os: platform.slug With this modification the output shows the following hostvars with the groups unchanged. Notice specifically line 3 that there is now the ansible_network_os is set to ios, which will match the Ansible Network OS that Ansible will use. I just make sure that the slug for the Platform name matches that of the primary automation system.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 \u0026#34;dcrtr001\u0026#34;: { \u0026#34;ansible_host\u0026#34;: \u0026#34;192.0.2.10\u0026#34;, \u0026#34;ansible_network_os\u0026#34;: \u0026#34;ios\u0026#34;, \u0026#34;custom_fields\u0026#34;: { \u0026#34;os_version\u0026#34;: null }, \u0026#34;device_roles\u0026#34;: [ \u0026#34;network\u0026#34; ], \u0026#34;device_types\u0026#34;: [ \u0026#34;iosv\u0026#34; ], \u0026#34;is_virtual\u0026#34;: false, \u0026#34;local_context_data\u0026#34;: [ null ], \u0026#34;manufacturers\u0026#34;: [ \u0026#34;cisco\u0026#34; ], \u0026#34;platforms\u0026#34;: [ \u0026#34;cisco_ios\u0026#34; ], \u0026#34;primary_ip4\u0026#34;: \u0026#34;192.0.2.10\u0026#34;, \u0026#34;regions\u0026#34;: [], \u0026#34;services\u0026#34;: [], \u0026#34;sites\u0026#34;: [ \u0026#34;datacenter01\u0026#34; ], \u0026#34;tags\u0026#34;: [ \u0026#34;virtual\u0026#34; ] }, Summary #The NetBox Inventory Plugin for Ansible is quite powerful. It provides for methods to group your devices, filter on data points maintained within NetBox, and to create additional hostvars. The parameters for the plugin are quite extensive and you should take a look at what makes sense for your environment or needs within the Ansible playbooks. There are a few defaults that are set to no, such as interfaces, that helps keep the data provided at a proper level. You can add more information to the hostvars that you may use in the playbooks.\nI think the plugin is awesome and has a lot of work that has gone into it. There is likely even more to come as you go.\n","date":"2020-11-29","permalink":"https://josh-v.com/netbox-ansible-inventory_plugin/","section":"Posts","summary":"\u003cp\u003eThe documentation can be found on \u003ca href=\"https://netbox-ansible-collection.readthedocs.io/en/latest/plugins/inventory/nb_inventory/netbox.netbox.nb_inventory_inventory.html\" target=\"_blank\" rel=\"noreferrer\"\u003eReadTheDocs\u003c/a\u003e. This is going to be starting out with the basics of the plugin and getting some sample output and to show how to form groups to be used.\u003c/p\u003e\n\u003cdiv class=\"flex rounded-md bg-primary-100 px-4 py-3 dark:bg-primary-900\"\u003e\n  \u003cspan class=\"pe-3 text-primary-400\"\u003e\n    \u003cspan class=\"icon relative inline-block px-1 align-text-bottom\"\u003e\u003csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003cpath fill=\"currentColor\" d=\"M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z\"/\u003e\u003c/svg\u003e\n\u003c/span\u003e\n  \u003c/span\u003e\n  \u003cspan class=\"dark:text-neutral-300\"\u003eThis post was created when NetBox was an open source project used often in my automation framework. I have moved on to using \u003ca href=\"https://www.nautobot.com\" target=\"_blank\" rel=\"noreferrer\"\u003eNautobot\u003c/a\u003e due to the project vision and providing a methodology that will drive network automation forward further. You may want to take a look at it yourself.\u003c/span\u003e\n\u003c/div\u003e\n\n\u003cblockquote\u003e\n\u003cp\u003eThis particular plugin \u003cstrong\u003eDOES NOT\u003c/strong\u003e require pynetbox to be used.\u003c/p\u003e","title":"NetBox Ansible Collection: Inventory - Starting Out"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/jekyll/","section":"Tags","summary":"","title":"Jekyll"},{"content":"Recently I had some discussions with Nick Russo on some URL redirection changes he was making for his content. I\u0026rsquo;m not going to take any of his thunder of what he is doing, and that is quite awesome. I decided that I wanted to take a look at that as well within my domain/blog using the Jekyll approach. This is going to be my short post regarding the steps I took to add the URL redirection setup to my personal blog page - josh-v.com.\nFirst Step - Research #Not being a native Ruby/gem person myself (Python + Ansible), the first thing I did was what anyone should do, see if there is prior art. So I did a search on your favorite search tool and there are a few references. Terrific this should be able to be done.\nThe first page that came up was https://github.com/hlaueriksson/jekyll-url-shortener. It itself was a little bit tougher for me to decipher but enough to get started. What really helped was the blog post that accompanied the page, which was the third result in the search - https://conductofcode.io/post/introducing-jekyll-url-shortener/.\nSecond Step - Testing #Next up was to generate some test code on my local Docker container that I could test the Jekyll blog out. This has been a terrific help. I had some issues getting Jekyll installed on my Mac, so I built a container to handle the testing and that has been working great. I sense a future post here.\nRedirect Page #First thing was to create a redirect page with front matter. I decided the best thing to start with was a redirect to my employer\u0026rsquo;s page. So I put this in:\n--- permalink: /ntc/ redirect_to: https://www.networktocode.com/ --- Once in I attempted to load the page to see if it would redirect. No go. That is where the second URL, the one referenced in the references section comes into play. I found that there were an additional two configuration items needed to help the Jekyll pages handle the redirection.\nAdditional Packages #The first thing was to add the package to the gemspec file. I went and found where the Jekyll plugins were referenced and found them inside the minimal-mistakes-jekyll.gemspec file. The blog theme I am using is Minimal Mistakes. So I added the following configuration line to the gemspec:\nspec.add_runtime_dependency \u0026#34;jekyll-redirect-from\u0026#34;, \u0026#34;~\u0026gt; 0.1\u0026#34; I then added the line to the plugins section of the _config.yml file (there are more plugins than just what\u0026rsquo;s listed here):\nplugins: - jekyll-redirect-from Once these two updates were made I was successfully getting redirects from my http://localhost:4000/ntc/ URL to https://www.networktocode.com.\nProduction #Next up was to put into production the changes. So I took the files over from the test instance into the GitLab project that I host this on. The same changes were made to the system and all is set to go.\nNext Up #There are several improvements that I\u0026rsquo;m looking to improve upon now that I\u0026rsquo;m all set in this current blog environment. First is to merge the development environment with my regular blog site. This should not be too bad, but as I was writing this post I realized the importance of this. The reasoning behind why I got in this state is because I was testing different blog platforms (Hugo vs Hashnode vs Jekyll vs Pelican).\nI also plan to re-evaluate if Disqus is the proper platform for my commenting. There are some other options out there, and I need to take a look for how low volume the blog is.\nThe last feature that I am looking that would have come in handy on this post is the copy of code snippets feature. That looks a bit more involved however. There are posts on how to do it, but I just need to take a little bit of time to test it out.\nSummary #In the end it was not too difficult to add a redirect URL page to my Jekyll blog. I plan to have a few links made available, and it is really to just help maintain a list of redirects to helpful content elsewhere and shorten URLs that become lengthy in social posting. Let me know your thoughts below, or if there is another method that I am missing. I may also start to look at some shorter domains, although two characters is probably the best I would be able to squeak out.\nResources #https://conductofcode.io/post/introducing-jekyll-url-shortener/\n","date":"2020-11-28","permalink":"https://josh-v.com/jekyll-url-redirection/","section":"Posts","summary":"\u003cp\u003eRecently I had some discussions with \u003ca href=\"https://twitter.com/nickrusso42518\" target=\"_blank\" rel=\"noreferrer\"\u003eNick Russo\u003c/a\u003e on some URL redirection changes he was making for his content. I\u0026rsquo;m not going to take any of his thunder of what he is doing, and that is quite awesome. I decided that I wanted to take a look at that as well within my domain/blog using the Jekyll approach. This is going to be my short post regarding the steps I took to add the URL redirection setup to my personal blog page - josh-v.com.\u003c/p\u003e","title":"Jekyll - Adding a URL Redirection"},{"content":"This is the first post as I start to look at the NetBox Ansible Collection. This is an impressive collection with modules for several of the NetBox applications, a query plugin, and an inventory plugin. This will take a deeper dive into several of the components of the inventory plugin, but not all of the options. The documentation for all of the collection can be found at:\nReadTheDocs: https://netbox-ansible-collection.readthedocs.io/en/latest/ Galaxy Page: https://galaxy.ansible.com/netbox/netbox This post was created when NetBox was an open source project used often in my automation framework. I have moved on to using Nautobot due to the project vision and providing a methodology that will drive network automation forward further. You may want to take a look at it yourself. This post is going to give information on how to install the collection as it may be applicable to every post in the series (as they get posted).\n(Update 2020-12-05) The corresponding YouTube video is here:\nInstallation #Installation is done via Ansible Galaxy. It is recommended to have the latest version of the collection when working on it as there are updates happening routinely. There is a Python requirement with the modules of the pynetbox package.\nIt does not matter which order you install these in, you just need to install both before you start using the module.\nInstallation - pynetbox #To install you execute the following to get the latest version of pynetbox:\npip install pynetbox --upgrade Installation - NetBox Collection #The collection is installed via Ansible Galaxy as a primary method to install. You can also install the collection manually from GitHub, but the galaxy method is the preferred method.\nansible-galaxy collection install netbox.netbox --force The addition of --force will have Ansible Galaxy install the latest version on top of what you may already have. If you already have a version of the collection installed, Galaxy will not overwrite what you already have.\nVerification of Installation #Once you have run the steps there are many ways to verify that the installation is completed successfully for the Python package. The one that I like to use is to execute a pip freeze | grep \u0026lt;package_name\u0026gt;. The execution looks like this on the current date:\npip3 freeze | grep pynetbox pynetbox==5.1.0 To verify that you have installed the NetBox Ansible Collection, you can execute the Ansible Doc command to get the current documentation. This is done as followed with the netbox_device module to verify that the docs load:\nansible-doc netbox.netbox.netbox_device If the module is not installed properly you will see, with a key in on the first line\n1 2 [WARNING]: module netbox.netbox.netbox_inventory not found in: ~/.local/lib/python3.7/site-packages/ansible/modules The output when I sent the stdout to a file is:\n\u0026gt; NETBOX.NETBOX.NETBOX_DEVICE (/Users/joshvanderaa/.ansible/collections/ansible_collections/netbox/netbox/plugins/modules/netbox_device.py) Creates, updates or removes devices from Netbox OPTIONS (= is mandatory): = data Defines the device configuration type: dict SUBOPTIONS: - asset_tag Asset tag that is associated to the device [Default: (null)] type: str - cluster Cluster that the device will be assigned to [Default: (null)] type: raw - comments Comments that may include additional information in regards to the device [Default: (null)] type: str - custom_fields must exist in Netbox [Default: (null)] type: dict - device_role Required if `state=present\u0026#39; and the device does not exist yet [Default: (null)] type: raw - device_type Required if `state=present\u0026#39; and the device does not exist yet [Default: (null)] type: raw - face Required if `rack\u0026#39; is defined (Choices: Front, front, Rear, rear)[Default: (null)] type: str - local_context_data Arbitrary JSON data to define the devices configuration variables. [Default: (null)] type: dict = name The name of the device type: str - platform The platform of the device [Default: (null)] type: raw - position The position of the device in the rack defined above [Default: (null)] type: int - primary_ip4 Primary IPv4 address assigned to the device [Default: (null)] type: raw - primary_ip6 Primary IPv6 address assigned to the device [Default: (null)] type: raw - rack The name of the rack to assign the device to [Default: (null)] type: raw - serial Serial number of the device [Default: (null)] type: str - site Required if `state=present\u0026#39; and the device does not exist yet [Default: (null)] type: raw - status The status of the device [Default: (null)] type: raw - tags Any tags that the device may need to be associated with [Default: (null)] type: list - tenant The tenant that the device will be assigned to [Default: (null)] type: raw - vc_position Position in the assigned virtual chassis [Default: (null)] type: int - vc_priority Priority in the assigned virtual chassis [Default: (null)] type: int - virtual_chassis Virtual chassis the device will be assigned to [Default: (null)] type: raw = netbox_token The token created within Netbox to authorize API access type: str = netbox_url URL of the Netbox instance resolvable by Ansible control host type: str - query_params This can be used to override the specified values in ALLOWED_QUERY_PARAMS that is defined in plugins/module_utils/netbox_utils.py and provides control to users on what may make an object unique in their environment. [Default: (null)] elements: str type: list - state Use `present\u0026#39; or `absent\u0026#39; for adding or removing. (Choices: absent, present)[Default: present] type: str - validate_certs If `no\u0026#39;, SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates. [Default: True] type: raw NOTES: * Tags should be defined as a YAML list * This should be ran with connection `local\u0026#39; and hosts `localhost\u0026#39; REQUIREMENTS: pynetbox AUTHOR: Mikhail Yohman (@FragmentedPacket), David Gomez (@amb1s1) METADATA: metadata_version: \u0026#39;1.1\u0026#39; status: - preview supported_by: community VERSION_ADDED_COLLECTION: netbox.netbox EXAMPLES: - name: \u0026#34;Test Netbox modules\u0026#34; connection: local hosts: localhost gather_facts: False tasks: - name: Create device within Netbox with only required information netbox_device: netbox_url: http://netbox.local netbox_token: thisIsMyToken data: name: Test Device device_type: C9410R device_role: Core Switch site: Main state: present - name: Create device within Netbox with empty string name to generate UUID netbox_device: netbox_url: http://netbox.local netbox_token: thisIsMyToken data: name: \u0026#34;\u0026#34; device_type: C9410R device_role: Core Switch site: Main state: present - name: Delete device within netbox netbox_device: netbox_url: http://netbox.local netbox_token: thisIsMyToken data: name: Test Device state: absent - name: Create device with tags netbox_device: netbox_url: http://netbox.local netbox_token: thisIsMyToken data: name: Another Test Device device_type: C9410R device_role: Core Switch site: Main local_context_data: bgp: \u0026#34;65000\u0026#34; tags: - Schnozzberry state: present - name: Update the rack and position of an existing device netbox_device: netbox_url: http://netbox.local netbox_token: thisIsMyToken data: name: Test Device rack: Test Rack position: 10 face: Front state: present RETURN VALUES: - device Serialized object as created or already existent within Netbox returned: success (when `state=present\u0026#39;) type: dict - msg Message indicating failure or info about what has been achieved returned: always type: str Summary #Overall the process for getting going with this collection is two steps, of installing the Python dependency and installing the collection via Ansible Galaxy. With these done, you are on your way to using the NetBox Ansible Collection in your environment.\nUp Next # Ansible Inventory ","date":"2020-11-28","permalink":"https://josh-v.com/collection_install/","section":"Posts","summary":"\u003cp\u003eThis is the first post as I start to look at the NetBox Ansible Collection. This is an impressive collection with modules for several of the NetBox applications, a query plugin, and an inventory plugin. This will take a deeper dive into several of the components of the \u003cstrong\u003einventory plugin\u003c/strong\u003e, but not all of the options. The documentation for all of the collection can be found at:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eReadTheDocs: \u003ca href=\"https://netbox-ansible-collection.readthedocs.io/en/latest/\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://netbox-ansible-collection.readthedocs.io/en/latest/\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eGalaxy Page: \u003ca href=\"https://galaxy.ansible.com/netbox/netbox\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://galaxy.ansible.com/netbox/netbox\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"flex rounded-md bg-primary-100 px-4 py-3 dark:bg-primary-900\"\u003e\n  \u003cspan class=\"pe-3 text-primary-400\"\u003e\n    \u003cspan class=\"icon relative inline-block px-1 align-text-bottom\"\u003e\u003csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003cpath fill=\"currentColor\" d=\"M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z\"/\u003e\u003c/svg\u003e\n\u003c/span\u003e\n  \u003c/span\u003e\n  \u003cspan class=\"dark:text-neutral-300\"\u003eThis post was created when NetBox was an open source project used often in my automation framework. I have moved on to using \u003ca href=\"https://www.nautobot.com\" target=\"_blank\" rel=\"noreferrer\"\u003eNautobot\u003c/a\u003e due to the project vision and providing a methodology that will drive network automation forward further. You may want to take a look at it yourself.\u003c/span\u003e\n\u003c/div\u003e\n\n\u003cp\u003eThis post is going to give information on how to install the collection as it may be applicable to every post in the series (as they get posted).\u003c/p\u003e","title":"NetBox Ansible Collection: Installation"},{"content":"This originates from a conversation had on Twitter about how to get the IP Prefix information from an IPAM tool, specifically\tNetBox using Ansible. There are a couple of methodologies to go through, and I had originally started down the path of using the URI module. Which could be done. The more elegant solution is to use the NetBox Ansible Collections to handle the logic for you! Let’s take a look.\nThank you to @ttl255 for the inspiration to the journey with the Collection!\nThis post was created when NetBox was an open source project used often in my automation framework. I have moved on to using Nautobot due to the project vision and providing a methodology that will drive network automation forward further. You may want to take a look at it yourself. The final playbook will be posted at the very bottom.\nSetup #The NetBox environment for this is NetBox 2.9.9. I have not tested with previous versions, but believe that this will work with 2.8.x as well. The Ansible execution environment is Ansible 2.9.15. This is making use of the Ansible NetBox Collections using the FQCN for the NetBox modules and NOT the core modules.\nThe first thing to note with this is that there are two variables in the environment to help this. They are the URL and TOKEN. This is good practice to help pass this through without much changes. The primary prefix at the top of the code should likely also move into the environment so that it can be changed.\nNETBOX_URL NETBOX_TOKEN The lab device that will be having the configuration updated is the edge router of my GNS3 lab. This had one more interface available on it for me to change and rather than change things up significantly, I just used this device.\nScenario #To get the next available Prefix from NetBox, and assign the IP address to the interface on the router. Success criteria for this scenario include:\nAllocate a /24 network prefix within NetBox for use Allocate the first usable (192.0.2.0/24 would be 192.0.2.1) as allocated within NetBox Add the IP address configuration to the router interface GigabitEthernet0/3 Add the network to area 0 of the OSPF configuration The device has already been created in NetBox with all of the necessary interfaces. A separate post will be created around adding devices to NetBox. To get started on this I suggest taking a look at a YouTube video that I did for the Ansible Minneapolis Meetup - https://www.youtube.com/watch?v=GyQf5F0gr3w and the corresponding [GitHub repo]\nNetBox Prefix Allocation #Here are the start the NetBox prefix allocation only has a single prefix defined at the start. Only 10.21.0.0/16, which is going to be the parent prefix.\nWe can see that there are no children prefixes and the current allocation is 0.\nCreating a Prefix within NetBox #The first step is to assign another prefix within NetBox. To do this the following task is used:\n- name: “10 - GET NEW PREFIX FROM NETBOX {{ primary_prefix }}” netbox.netbox.netbox_prefix: netbox_url: “{{ lookup(‘env’, ‘NETBOX_URL’) }}” netbox_token: “{{ lookup(‘env’, ‘NETBOX_TOKEN’) }}” data: parent: “{{ primary_prefix }}” prefix_length: 24 state: present first_available: yes register: prefix_info This task is going to take from the parent prefix and allocate a prefix of length 24. This states to take the first available prefix. Executing the playbook we are building with the -vv option and the stdout_callback=yaml in the ansible.cfg file you can see the output:\nchanged: [rtr-edge] =\u0026gt; changed=true msg: prefix 10.21.5.0/24 created prefix: created: \u0026#39;2020-11-22\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; family: 4 id: 25 is_pool: false last_updated: \u0026#39;2020-11-22T15:57:13.641224Z\u0026#39; prefix: 10.21.5.0/24 role: null site: null status: active tags: [] tenant: null url: http://netbox.josh-v.com/api/ipam/prefixes/25/ vlan: null vrf: null This response when registered will provide with the Prefix ID, prefix itself, and any additional items that may have been set for your NetBox environment. After running this a few times and this demo being the sixth execution this is now what the NetBox environment looks like for prefixes:\nIP Address Allocation #After getting the first task to allocate the prefix, next up is to assign the IP address from the prefix. This task allocates an IP address from the prefix that was just previously allocated.\n1 2 3 4 5 6 7 8 - name: \u0026#34;20 - ALLOCATE IP ADDRESS FOR THE ROUTER INTERFACE\u0026#34; netbox.netbox.netbox_ip_address: netbox_url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_URL\u0026#39;) }}\u0026#34; netbox_token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; data: prefix: \u0026#34;{{ prefix_info[\u0026#39;prefix\u0026#39;][\u0026#39;prefix\u0026#39;] }}\u0026#34; state: new register: ip_address_info On line 6 you see that the prefix gathered is mentioned via the variable. This is taken from the output that was seen from the NetBox Prefix allocation.\nThis then looks like this for the output:\nchanged: [rtr-edge] =\u0026gt; changed=true ip_address: address: 10.21.5.1/24 assigned_object: null assigned_object_id: null assigned_object_type: null created: \u0026#39;2020-11-22\u0026#39; custom_fields: {} description: \u0026#39;\u0026#39; dns_name: \u0026#39;\u0026#39; family: 4 id: 23 last_updated: \u0026#39;2020-11-22T15:57:14.833379Z\u0026#39; nat_inside: null nat_outside: null role: null status: active tags: [] tenant: null url: http://netbox.josh-v.com/api/ipam/ip-addresses/23/ vrf: null msg: ip_address 10.21.5.1/24 created Taking a look at the NetBox Prefix View for the 10.21.5.0/24 network this is what you see:\nYou can see that there is a single IP address allocated.\nVariable Shortening #The next task in the Playbook is to shorten some of the variables. This is purely for visualization purposes. In order to not have long lines in the coming tasks, the following was done to create shorter line lengths:\n1 2 3 4 - name: \u0026#34;30 - SET FACTS TO ASSIGN IP ADDRESS TO CISCO IOS ROUTER\u0026#34; set_fact: ip_address: \u0026#34;{{ ip_address_info[\u0026#39;ip_address\u0026#39;][\u0026#39;address\u0026#39;] | ipaddr(\u0026#39;ip\u0026#39;) }}\u0026#34; netmask: \u0026#34;{{ ip_address_info[\u0026#39;ip_address\u0026#39;][\u0026#39;address\u0026#39;] | ipaddr(\u0026#39;netmask\u0026#39;) }}\u0026#34; Apply the Cisco Configuration #Now that there is an IP address and prefix available, and assigned within NetBox, the next step is to add the configuration to the device. Since this is primarily a focus on the NetBox side of things this will be short.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 # DEPLOY THE INFORMATION TO THE ROUTER - name: \u0026#34;100 - ADD IP ADDRESS INFORMATION TO THE ROUTER\u0026#34; ios_config: parents: \u0026#34;interface GigabitEthernet0/3\u0026#34; lines: - \u0026#34;ip address {{ ip_address }} {{ netmask }}\u0026#34; save_when: changed - name: \u0026#34;110 - ADD ROUTING CONFIGURATION\u0026#34; ios_config: parents: \u0026#34;router ospf 1\u0026#34; lines: - \u0026#34;network {{ ip_address_info[\u0026#39;ip_address\u0026#39;][\u0026#39;address\u0026#39;] | ipaddr(\u0026#39;network\u0026#39;) }} {{ netmask }} area 0\u0026#34; save_when: changed Lines 2-7 are the applying of the configuration to the interface to be used. Lines 9-14 are used to add the network statement to OSPF for the prefix. With this done the interface is now configured and routing is setup.\nTASK [100 - ADD IP ADDRESS INFORMATION TO THE ROUTER] **************************************************************************************************************** changed: [rtr-edge] =\u0026gt; changed=true ansible_facts: discovered_interpreter_python: /usr/bin/python banners: {} commands: - interface GigabitEthernet0/3 - ip address 10.21.5.1 255.255.255.0 updates: - interface GigabitEthernet0/3 - ip address 10.21.5.1 255.255.255.0 TASK [110 - ADD ROUTING CONFIGURATION] ******************************************************************************************************************************* changed: [rtr-edge] =\u0026gt; changed=true banners: {} commands: - router ospf 1 - network 10.21.5.0 255.255.255.0 area 0 updates: - router ospf 1 - network 10.21.5.0 255.255.255.0 area 0 Production Ready #This is a quick demo and has some hand holding that needs to be done for it. There does need to be some Atomic handling added yet to make this a rock solid playbook. In a future post I will also cover how to simplify the save_when feature to help speed things up as well. This right now will save the configuration on each change. This should get simplified down to a single save execution.\nFinal Playbook #Here is what the final playbook looks like at the moment, again not completely production ready, but is a good starting point.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 --- - name: \u0026#34;PLAY 1 - ASSIGN PREFIXES FOR HOST\u0026#34; gather_facts: no connection: network_cli hosts: rtr-edge vars: primary_prefix: \u0026#34;10.21.0.0/16\u0026#34; tasks: - name: \u0026#34;LOCALHOST BLOCK\u0026#34; delegate_to: localhost block: - name: \u0026#34;10 - GET NEW PREFIX FROM NETBOX {{ primary_prefix }}\u0026#34; netbox.netbox.netbox_prefix: netbox_url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_URL\u0026#39;) }}\u0026#34; netbox_token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; data: parent: \u0026#34;{{ primary_prefix }}\u0026#34; prefix_length: 24 state: present first_available: yes register: prefix_info - name: \u0026#34;20 - ALLOCATE IP ADDRESS FOR THE ROUTER INTERFACE\u0026#34; netbox.netbox.netbox_ip_address: netbox_url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_URL\u0026#39;) }}\u0026#34; netbox_token: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NETBOX_TOKEN\u0026#39;) }}\u0026#34; data: prefix: \u0026#34;{{ prefix_info[\u0026#39;prefix\u0026#39;][\u0026#39;prefix\u0026#39;] }}\u0026#34; state: new register: ip_address_info - name: \u0026#34;30 - SET FACTS TO ASSIGN IP ADDRESS TO CISCO IOS ROUTER\u0026#34; set_fact: ip_address: \u0026#34;{{ ip_address_info[\u0026#39;ip_address\u0026#39;][\u0026#39;address\u0026#39;] | ipaddr(\u0026#39;ip\u0026#39;) }}\u0026#34; netmask: \u0026#34;{{ ip_address_info[\u0026#39;ip_address\u0026#39;][\u0026#39;address\u0026#39;] | ipaddr(\u0026#39;netmask\u0026#39;) }}\u0026#34; # DEPLOY THE INFORMATION TO THE ROUTER - name: \u0026#34;100 - ADD IP ADDRESS INFORMATION TO THE ROUTER\u0026#34; ios_config: parents: \u0026#34;interface GigabitEthernet0/3\u0026#34; lines: - \u0026#34;ip address {{ ip_address }} {{ netmask }}\u0026#34; save_when: changed - name: \u0026#34;110 - ADD ROUTING CONFIGURATION\u0026#34; ios_config: parents: \u0026#34;router ospf 1\u0026#34; lines: - \u0026#34;network {{ ip_address_info[\u0026#39;ip_address\u0026#39;][\u0026#39;address\u0026#39;] | ipaddr(\u0026#39;network\u0026#39;) }} {{ netmask }} area 0\u0026#34; save_when: changed ","date":"2020-11-22","permalink":"https://josh-v.com/netbox_ansible_allocate_prefix_ipaddress/","section":"Posts","summary":"\u003cp\u003eThis originates from a conversation had on Twitter about how to get the IP Prefix information from an IPAM tool, specifically\tNetBox using Ansible. There are a couple of methodologies to go through, and I had originally started down the path of using the URI module. Which could be done. The more elegant solution is to use the NetBox Ansible Collections to handle the logic for you! Let’s take a look.\u003c/p\u003e","title":"Ansible + NetBox: Getting Next Prefix / IP"},{"content":"There does not appear to be a complete set of documentation pieces available for setting up Prometheus on the Home Assistant platform. This post will take you along on my journey of setting up the Home Assistant to get metrics from it. The link for the documentation is a good start at getting Prometheus installed. https://www.home-assistant.io/integrations/prometheus/\nStarting Prometheus # Edit your configuration.yaml file Add in a key of prometheus: Add in any parameters you may need, but just they key alone is enough to start the exporter Once you have started the exporter, I was still getting a 404 not found. So I did restart the Home Assistant.\nInstallation of Prometheus Endpoint #The first thing that is different from most of the times that I have used Prometheus is that this implementation puts the information behind an authorization page. This is not so bad, but it definitely threw me for a loop for a short bit. There are a few options that I looked at for getting past the authentication issue\nLegacy Tokens #The first thing I looked at was the legacy tokens. But quickly moved beyond this as Legacy wording is key. It is going away in the future and since this is a new setup, I didn\u0026rsquo;t want to use anything legacy.\nTrusted Networks \u0026amp; Authentication Providers #I started to take a look at auth_providers and specifically the Trusted Networks aspect. I would love to be able to see what the Prometheus HTTP page looks like. However, making changes to the authentication mechanisms seemed like overkill for what I was looking to do.\nContinuing to look at the Prometheus example:\n# Example Prometheus scrape_configs entry - job_name: \u0026#39;hass\u0026#39; scrape_interval: 60s metrics_path: /api/prometheus # Legacy api password params: api_password: [\u0026#39;PASSWORD\u0026#39;] # Long-Lived Access Token bearer_token: \u0026#39;your.longlived.token\u0026#39; scheme: https static_configs: - targets: [\u0026#39;HOSTNAME:8123\u0026#39;] I kept finding the comment of \u0026ldquo;Long-Lived Access Token\u0026rdquo;. This seems ideal for what we would want in an API based application.\nLong Lived Access Token #This seems like the ideal state to get into. Setting this token is outlined https://developers.home-assistant.io/docs/auth_api/#long-lived-access-token. To get at the profile page for your user:\nSelect your username on the lower left Scroll to the very bottom Select Create Token Save this token to your password manager → It will go away from sight Setup Prometheus #The final configuration that I used for the Prometheus scraping was set:\n- job_name: \u0026#39;homeassistant\u0026#39; scrape_interval: 60s metrics_path: /api/prometheus bearer_token: \u0026lt;token\u0026gt; static_configs: - targets: - \u0026#34;192.0.2.10:8123\u0026#34; Substitute \u0026ldquo;192.0.2.10\u0026rdquo; with the IP address/name of your home assistant host.\nVerify Metrics #Once the scraping has been setup, go to your Prometheus end point, then search. My devices started showing up immediately with the measurement prefix of hass when there was nothing more specifically setup.\nSummary #Gathering metrics about your Home Automation platform is a fun thing to do, and to continue to gather experience on how to work with the modern metrics tooling. Whether the database is Prometheus or InfluxDB or other option, this can be helpful. This walked through looking at a couple of options for setting up Prometheus. Then once Prometheus was setup on the Home Assistant side, went and setup the Prometheus scraping configuration. Finally how to verify that you are getting appropriate metrics ingested.\nNote that I look to have a post in the future on why Home Assistant, where I have installed Home Assistant (there is some thought on this), and perhaps some of my graphs that I have being stored in Grafana at this time. I also hope to go through and setup the same for an InfluxDB instance as well.\nHope this has helped. Leave a comment or give a thumbs up or down!\nJosh\n","date":"2020-11-21","permalink":"https://josh-v.com/homeassistant-prometheus/","section":"Posts","summary":"\u003cp\u003eThere does not appear to be a complete set of documentation pieces available for setting up Prometheus on the Home Assistant platform. This post will take you along on my journey of setting up the Home Assistant to get metrics from it. The link for the documentation is a good start at getting Prometheus installed. \u003ca href=\"https://www.home-assistant.io/integrations/prometheus/\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://www.home-assistant.io/integrations/prometheus/\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"starting-prometheus\" class=\"relative group\"\u003eStarting Prometheus \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#starting-prometheus\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003col\u003e\n\u003cli\u003eEdit your \u003ccode\u003econfiguration.yaml\u003c/code\u003e file\u003c/li\u003e\n\u003cli\u003eAdd in a key of \u003ccode\u003eprometheus:\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eAdd in any parameters you may need, but just they key alone is enough to start the exporter\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eOnce you have started the exporter, I was still getting a 404 not found. So I did restart the Home Assistant.\u003c/p\u003e","title":"Home Assistant Prometheus Exporting Setup"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/homeassistant/","section":"Tags","summary":"","title":"Homeassistant"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/prometheus/","section":"Tags","summary":"","title":"Prometheus"},{"content":"I\u0026rsquo;ve changed a few things on the site. Sorry about that! URLs have changed. Over the past week or so I have been working through making some what originally were small updates to the blog, that turned into a little too much effort. I was hoping to add a little bit of polish to the site while keeping the content in place. Earlier in 2020, maybe even back in 2019 I had become aware of Hashnode from the posts of David Flores - aka NetPanda who is on the Hashnode side at https://davidban77.hashnode.dev/. I liked many things that the blogging site has to offer. From a very quick up and running, to having a strong start of a community.\nHashnode Trial #I decided to move the blog to Hashnode as a let\u0026rsquo;s get started. I found it is easy to move the site as they already supported Markdown, which is what I write my blogs in already. The only downside that I originally saw as that there were no line numberings on the code blocks. I can live without that, but I still did desire it. I added my domain name and made the necessary DNS updates and it started to work out.\nDownsides of Moving to Hashnode #In the process I found that my first downside was that I lost some of my search engine rankings. One of the more popular over time posts was now gone from the search list. This is better as now my posts with Network to Code and some more recent post updates are showing up at the top of the list. Just wanted to make sure that was known.\nAn unexpected downside of the move was that Hashnode appeared to enable HSTS on the domain josh-v.com. The implications of this is that on my development hosts that get the domain extension josh-v.com are then also expecting to be HTTPS. And the browsers automatically forwarded to the HTTPS domain. So I just went ahead and moved them to a new domain. Nothing that I couldn\u0026rsquo;t overcome, but it took a short bit to figure that out.\nMove Away from Hashnode #In the end I found that when my domain was moved over there was not a way to customize the RSS feed. I chose to move back for that reason. I also want to be able to have a little more control on my domain about which URLs are HTTPS and which ones are not. And there are some other implications for the future as well. I moved back to the GitLab Pages that was still in place. This migration was an easy undo action.\nHugo Evaluation #Hugo has a tremendous upside to it as the static site generator platform. It is written in Go, which helps with its speed, and can help you work with GoLang learning. There is an awesome centralized theme gallery for you to view possible themes to apply, and their features/code. It is extremely flexible.\nThe one thing that I could not get to work out right was the image sizes on the site. When I tested locally and on GitHub pages (note that my main site is hosted on GitLab pages) the images would not resize to match the article. I found several posts that indicated to make a shortcode and then call the image that way, however that didn\u0026rsquo;t seem to resolve my issues. So after a couple of evenings of attempting to figure this out that is when I turned back to check into the Jekyll themes arena.\nJekyll Themes #The one downside is there is not a great system for themes like Hugo has, but there were some theme galleries around. I decided first to take a look at Minimal Mistakes to see if there was something that I could do. And there was. There had been many updates to the theme, and it has had an extreme amount of flexibility and capabilities added on. I got the theme up and running and then tested out what my existing blog posts would look like. They looked terrific, and just what I was looking for. I had a few small tweaks to make. I also needed to update the GitLab CI process for the new theme version. This has been a pleasant experience thus far. The couple of downsides that I see are that I need to research how to add a copy button to the code snippets where I would like them. I can handle that. And then the deployment length has some added pieces due ot the Ruby install process. I can live with this for now and have some paths forward to take.\nI love the new layouts in it. I like the table of contents options that will be on most if not all of my posts. I have a few tweaks on some presentations which shouldn\u0026rsquo;t be too hard.\nThe only downside thus far to the move to the newer version (and maybe this was my fault from 2+ years ago) was that my posts now have new URLs. So if I was counting on someone bookmarking a post, these have now changed. I hope that these URLs will now remain in the future as they seem consistent on platforms from Hashnode, Hugo, and Jekyll now.\nSo for now, I am staying on Jekyll.\nPython - Pelican #I\u0026rsquo;d love to look at moving this blog to being a Pelican theme. However, when I researched some of the features I was looking for they just didn\u0026rsquo;t seem to have it yet. I know Python the best and can contribute at times, but now is not the time for me to be contributing to this. I have other priorities that I know I won\u0026rsquo;t be able to tackle this type of adventure at this time. Maybe some point in the future.\nHashnode - Good Platform #I did a small amount of negative points around Hashnode. There is a TON of good things about the platform. I do intend to keep an eye on how the platform moves on. I have opened several feature requests to hopefully get the last pieces that I would need to move over to them. There are several awesome things that are going on within the platform:\nUsing modern techniques, writing within Markdown and providing a modern browser interface is good Easy, no research needed features Image resizing! This is why I didn\u0026rsquo;t move to Hugo Nice layouts pre-built I absolutely would look at Hashnode if I were starting out on my blog. There is a great amount of features available. I had the opportunity to stick with something else.\nTLDR # Moved to Hashnode, had some quirks that I didn\u0026rsquo;t like Moved back to GitLab Pages! Tried hugo, but couldn\u0026rsquo;t get images to work right Moved back to Jekyll with an updated theme and here we are! ","date":"2020-11-18","permalink":"https://josh-v.com/blog_update_v2/","section":"Posts","summary":"\u003cp\u003eI\u0026rsquo;ve changed a few things on the site. Sorry about that! URLs have changed. Over the past week or so I have been working through making some what originally were small updates to the blog, that turned into a little too much effort. I was hoping to add a little bit of polish to the site while keeping the content in place. Earlier in 2020, maybe even back in 2019 I had become aware of Hashnode from the posts of \u003ca href=\"https://twitter.com/davidban77\" target=\"_blank\" rel=\"noreferrer\"\u003eDavid Flores - aka NetPanda\u003c/a\u003e who is on the Hashnode side at \u003ca href=\"https://davidban77.hashnode.dev/\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://davidban77.hashnode.dev/\u003c/a\u003e. I liked many things that the blogging site has to offer. From a very quick up and running, to having a strong start of a\ncommunity.\u003c/p\u003e","title":"2020 Blog Update"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/hashnode/","section":"Tags","summary":"","title":"Hashnode"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/hugo/","section":"Tags","summary":"","title":"Hugo"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/gns3/","section":"Tags","summary":"","title":"Gns3"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/network-simulator/","section":"Tags","summary":"","title":"Network Simulator"},{"content":"In an earlier post I took a look at how to setup EVE-NG to get access to virtualized network devices and topologies. This post is going to take a look at how to setup GNS3 systems to allow access.\nIn the overall topology that is a \u0026ldquo;home\u0026rdquo; network sits a device that supports a routing protocol, usually either OSPF or BGP. What is known to work at an inexpensive price point is the Ubiquiti EdgeRouter X.\nGNS3 Setup #This post is not a post on how to setup GNS3, it is meant to help you start to access devices. This tutorial is running a GNS3 VM on a remote host. Take a look at the GNS3 docs on how to install GNS3 specifically.\nGNS3 Configuration #The topology item used to create the connection to the production network is the device type of Cloud. Add a cloud with the general connection and the device will have connectivity to your local network.\nNext setup a router (in this instance using a Cisco vIOS image - licensed item). Connect that device to the cloud that was added to the topology. In this particular setup, DHCP is being used.\nRouter Configuration #rtr-edge#show run interface GigabitEthernet0/0 Building configuration... Current configuration : 110 bytes ! interface GigabitEthernet0/0 ip address dhcp duplex auto speed auto media-type rj45 no cdp enable end With the device getting an address, the device also gets a floating default static route imported to match the DNS request:\nrtr-edge#show ip route Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2 E1 - OSPF external type 1, E2 - OSPF external type 2 i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2 ia - IS-IS inter area, * - candidate default, U - per-user static route o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP a - application route + - replicated route, % - next hop override, p - overrides from PfR Gateway of last resort is 192.0.2.1 to network 0.0.0.0 S* 0.0.0.0/0 [254/0] via 192.0.2.1 192.0.2.0/24 is variably subnetted, 2 subnets, 2 masks C 192.0.2.0/24 is directly connected, GigabitEthernet0/0 L 192.0.2.163/32 is directly connected, GigabitEthernet0/0 Verify that you have access to the Internet by using ICMP to test.\nrtr-edge#ping 1.1.1.1 Type escape sequence to abort. Sending 5, 100-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds: !!!!! Success rate is 100 percent (5/5), round-trip min/avg/max = 13/16/18 ms Setup Automation #By using OSPF you are able to setup networks and advertise them back into your \u0026ldquo;production\u0026rdquo;/\u0026ldquo;home\u0026rdquo; network. With the network being advertised you can then setup your hosts with addressing that would have access from the network.\nIt is recommended that you test SSH/API connectivity into the GNS3 environment manually.\nSummary #With the tools of GNS3, EVE-NG, Cisco CML, and VRNetLab you have significant choice in looking at tools that will help you to level up your skills in Network Automation.\nHope that this may help you out in some way!\nJosh\n","date":"2020-11-01","permalink":"https://josh-v.com/practicing_with_gns3/","section":"Posts","summary":"\u003cp\u003eIn an earlier post I took a look at how to setup EVE-NG to get access to virtualized network devices and topologies. This post is going to take a look at how to setup GNS3 systems to allow access.\u003c/p\u003e\n\u003cp\u003eIn the overall topology that is a \u0026ldquo;home\u0026rdquo; network sits a device that supports a routing protocol, usually either OSPF or BGP. What is known to work at an inexpensive price point is the \u003ca href=\"https://www.ui.com/edgemax/edgerouter-x/\" target=\"_blank\" rel=\"noreferrer\"\u003eUbiquiti EdgeRouter X\u003c/a\u003e.\u003c/p\u003e","title":"Practicing Network Automation with GNS3"},{"content":"One of the appealing features that I have towards working with Ansible is that it is able to automate components across the entire Enterprise IT stacks. Rather than having to stitch together your network, server, and desktop automation tools, there is at least one automation tool that will work with just about your entire IT stack. In this I will take a high level overview of some of the features that are there for you to explore.\nAnsible for Network Automation #The first area that I will be brief on is from the network side of things. I am a long time network engineer and that is close to my heart.\nAnsible is agentless and uses SSH as it\u0026rsquo;s communication path. That leads well to interacting with some of the more legacy network devices. Ansible also supports using the newer tooling of APIs from devices, so until all of your entire Enterprise IT infrastructure supports API calls for automation, Ansible can definitely fit the bill.\nThe other interesting shift in the modules for networking is the move towards helping with intent based configuration. The newer modules being written by the Ansible team have an absolute intent configuration to them. This being that you need to send through your entire defined state to the modules, or else they will be seen as intended to have a blank configuration. The modules will then configure the devices as such. To see more on that look at my post on the interfaces module.\nIf you are running an OS in your network that is Linux based, then you are in luck as well! Continue to the next section about Linux automation.\nAnsible for Linux Server Automation #This is the original purpose of Ansible. It was built to automate Linux systems. Many of the core modules that will be part of the Ansible base moving forward are modules that you use to manage Linux systems. This is an absolute fit for the market. The times that I have written Ansible Playbooks for Linux OS it has been a joy to work with and works very smoothly.\nAnsible for Docker #Ansible is able to automate your Docker environment as well. With support for both Docker containers and docker-compose functionality. This will help you through your life cycle of Docker containers. Although it does not get to the level of what Kubernetes will do from an orchestration level without some level of effort.\nAnsible for Windows #Ansible for Windows is a thing! Although I do think it takes a little more effort to get off the ground than even the network side. You need to enable WinRM on the Windows host for the functionality to work. After that under the hood instead of using Python Ansible is leveraging PowerShell code to interact with Windows OS. So yes, you can automate Windows devices.\nAnsible for MacOS #Being a *nix operating system, you can manage your Mac deployment with Ansible. Now you just need to make sure the hosts are online when executing. So there isn\u0026rsquo;t an out of the box check in agent within Ansible. That\u0026rsquo;s what makes Ansible awesome for networking is that it is agentless. Take a look at Ansible for your Macs.\nAnsible for Cloud #I will need to find the link again, but Ansible is one of the largest percentage increase in tools to manage your cloud environments. There are quite the number of modules available for the leading public cloud (and private) providers. If there isn\u0026rsquo;t a specific module, one characteristic of a good cloud environment these days is the ability to have a REST API. With Ansible you can leverage the URI module for this.\nOn the cloud module front, take a look at the table below. This is the number of modules that there are within Ansible for managing their cloud environment. In my opinion, that is quite a bit and can get you what you need.\nCloud Module Count AWS 45+ Azure 169 Oracle Cloud 30 “services” Google 153 Digital Ocean 22 Rackspace 26 Avi Networks 65 VMWare 140+ Downside of Ansible #The biggest downside to leveraging Ansible would be the timeliness of execution. If you are looking for speed on execution, then Ansible would either need some tweaks (such as installing mitogen).\nWhen it comes to automation, in my book the first gain is not speed. Speed is a by product of not having to do rework and to move verification into an automated state. The real gain is by having a consistent environment. Then moving your operations into Playbooks, which many may have heard of a \u0026ldquo;runbook\u0026rdquo; which defines the process. Now your process is defined into a system that actually does something.\nSummary #In my opinion Ansible is still a right tool for the job when it comes to automating both your network environments, and your entire enterprise IT stack. I encourage you to take a look, evaluate other options as well. I hope this summary may be helpful. Let me know your thoughts in the comments or on Twitter/LinkedIn.\nJosh\n","date":"2020-08-11","permalink":"https://josh-v.com/ansible_for_enterprise/","section":"Posts","summary":"\u003cp\u003eOne of the appealing features that I have towards working with Ansible is that it is able to\nautomate components across the entire Enterprise IT stacks. Rather than having to stitch together\nyour network, server, and desktop automation tools, there is at least one automation tool that will\nwork with just about your entire IT stack. In this I will take a high level overview of some of the\nfeatures that are there for you to explore.\u003c/p\u003e","title":"Ansible for Enterprise"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/enterprise-design/","section":"Tags","summary":"","title":"Enterprise Design"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/docker/","section":"Tags","summary":"","title":"Docker"},{"content":"Docker is a terrific solution for making a consistent working environment. It\u0026rsquo;s been about a year or so since I built my very first own Docker container. I had always known why you use a container, but was always intimidated too much so to even get started. I am glad that I did get started and am off on my journey of using Docker containers. Let me jump into the problem and why? Couple the recent experiences with Docker, and the upcoming move to slim down Ansible and install Collections for most Network Automation modules, I thought it would be a good thing to get a write up done.\nProblem #From a Network Automation standpoint, there is much change still occurring in the tooling ecosystem. First that January 1, 2020 marked the end of support for Python 2.7. Yet there are still many setups that require Python 2.7.\nUpcoming Ansible is changing the behavior from a full batteries included for Network Automation tooling, moving over to a base package where you install Collections on top of it. This is going to be, in my opinion, a second driver for really digging into using containers for your enterprise automation envrionment.\nWhy is it a Solution? #Ansible Experience #This is a solution because it helps that you do your development work within a container. When you run the command ansible-playbook you are getting the ansible-playbook executable that is built into the container image. If outside of a container, there are several things that could happen. You may install some things with Python 2, could with Python 3, which version of Python 3? Which version of Python does the executables associated with Ansible reference? There are several methods to get multiple versions of Python to be executing on your system. Installing Ansible dependencies into the wrong Python PIP can definitely hamper it.\nI Don\u0026rsquo;t Use Ansible, Why Then? #This is an answer still for both Ansible and Python a like. One, if you mess up an installation you just rebuild the container image. Yes, it is shorter to just delete a virtual environment as well. When you go to install the Python application into the customer environment, you get portability, as you bring your own Python installation with the container. As long as the customer supports containers it adds portability. No more differing sets of instructions. Just a simple, docker command or update of a docker-compose file and away you go.\nPortability #One of the tenants of the 12-factor app for developing modern apps is to ask the question if you could open source your project tomorrow. If developing in virtual environments you will still need to have a setup instruction set that could miss something that you just have in your environment. With a container you bring a blank OS to the table inside of the container. You install explicitly everything you need to get the app up and running.\nRHEL vs Ubuntu vs MacOS vs Any Other Linux OS #The next thing about containers if you are going to interact with a file system in any way is that you get consistency for your app. No longer do you need to understand how to interact with MacOS with this command, and CentOS is this command, and Ubuntu is this other command. You choose the base OS image, and interact with it as such.\nGetting Started #My methodology for getting started with Docker was to read some getting started guides, and adapt them for my Network Automation flavor. I\u0026rsquo;m going to try to walk you along so you can learn from some of the things I learned along the way, and improve upon them.\nI started off with installing Docker Desktop on my Mac. The best way to get started is to install Docker onto your OS of choice. The installation guide is https://docs.docker.com/get-docker/. There is also a HomeBrew package available for installing Docker as well.\nDocker Labs on the Internet #There are several resources available on the Internet for you to get experience with Docker.\nCisco DevNet Learning Labs Play with Docker Labs I encourage you to take a look at these if you do not want to get started on your own in your own machine. The following examples will be done on your local machine.\nWriting A Dockerfile #A Dockerfile is the set of instructions of how to build your container. So if you want to follow along, create a directory anywhere that is accessible via command line called docker_test. Take the following and paste it into a file called Dockerfile.\nFROM python:3.8.3-slim-buster RUN apt-get update \u0026amp;\u0026amp; apt-get install sshpass vim -y COPY . /local WORKDIR /local RUN pip install -r requirements.txt RUN ansible-galaxy collection install -r requirements.yml What this is doing:\nLine Number Outline 1 Selecting the base image, this can be found on https://hub.docker.com, searching Python 3 Installing sshpass and vim via apt. Updating the apt repo list first. Being a slim image, VIM is not pre-installed 5 Copies everything in the local directory into a directory /local 6 Changes the working directory, similar to cd in many OSes 8 Installs Python packages from the local requirements.txt file that was copied on line 5 9 Uses ansible-galaxy to install Galaxy collections from the local requirements.yml file To have this work right, lets use this as an opportunity to test out installing the new version of Ansible into a container, by pip installing ansible-base.\nIn order to build this container, you will need to have a requirements file ready to go for both Python and Ansible Galaxy. So to this effort, here are those two files with a few packages:\nrequirements.txt # 1 ansible-base The only Python package going to install this for is ansible-base which will install the current beta versions of Ansible Base 2.10.\nrequirements.yml # --- # Collection Installations collections: - name: cisco.asa version: 1.0.0 This has a few more keys to the requirements.yml file that Ansible Galaxy will install with. The YML file first has a key of collections. This is because you can use the file to install both roles and collections, not just collections. Here this will install the 1.0.0 release of the Ansible modules for the Cisco ASA platform. You will likely need to add additional roles to here. To get the name you leverage https://galaxy.ansible.com to search and find the modules that you would install.\nHelper #The last piece I\u0026rsquo;d like to provide some info on as well is a helper file. Many of the *nix systems have Make available to them. If unable to use Make on your filesystem, I will suggest to take a look at the Python package invoke. Invoke does have more flexibility than make as it is written in Python, but for this demo I want to use Make so you can see the command line commands that are used.\nThe arguments for Make are made available by defining information in a Makefile. Here is the Makefile that I will be using for this:\nIMG_NAME=jvanderaa/network_automation IMG_VERSION=2.0-rc2 .DEFAULT_GOAL := cli .PHONY: build build: docker build -t $(IMG_NAME):$(IMG_VERSION) . .PHONY: cli cli: docker run -it \\ -v $(shell pwd):/local \\ -w /local \\ $(IMG_NAME):$(IMG_VERSION) bash Line Number Action 1 Defining a variable for image name, here jvanderaa/network_automation 2 Defining a variable for IMG_VERSION so you can version your Docker containers 3 Setting a default goal so you can just type make and that is what will be done 5 .PHONY is saying that this is a phony file that is upcoming. It is best practice to include but not required 6 The key build: is what will be executed with the make build command 7 The actual command, it is tabbed in. You MUST NOT use spaces and MUST use tabs with Make 9 Definition of .PHONY for CLI 10 Defining a key of cli for make cli or just make due to the default goal defined 11 Start of the Docker run command, which includes mapping the local directory into the container directory so you can make live updates, changing the working directory, and launching bash Building The Container #First in my container image list I have no containers (I just pruned them all, and they are now all deleted with the command docker system prune -a):\n1 2 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE With no containers, I execute the command from the Makefile of make build. This will download all of the layers, run the apt installations, pip install, and galaxy install. The output is below with many of the lines removed.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 docker build -t jvanderaa/network_automation:2.0-rc2 . Sending build context to Docker daemon 5.12kB Step 1/6 : FROM python:3.8.3-slim-buster 3.8.3-slim-buster: Pulling from library/python 8559a31e96f4: Pull complete 62e60f3ef11e: Pull complete 93c8ae153782: Pull complete ea222f757df7: Pull complete e97d3933bbbe: Pull complete Digest: sha256:938fd520a888e9dbac3de374b8ba495cc50fe96440030264a40f733052001895 Status: Downloaded newer image for python:3.8.3-slim-buster ---\u0026gt; 9d84edf35a0a Step 2/6 : RUN apt-get update \u0026amp;\u0026amp; apt-get install sshpass vim -y ---\u0026gt; Running in 66e37abb454a [ I REMOVED A BUNCH OF LINES HERE] Step 6/6 : RUN ansible-galaxy collection install -r requirements.yml ---\u0026gt; Running in 94ca0bb3f3c9 Starting galaxy collection install process Process install dependency map Starting collection install process Installing \u0026#39;cisco.asa:1.0.0\u0026#39; to \u0026#39;/root/.ansible/collections/ansible_collections/cisco/asa\u0026#39; Installing \u0026#39;ansible.netcommon:1.0.0\u0026#39; to \u0026#39;/root/.ansible/collections/ansible_collections/ansible/netcommon\u0026#39; Removing intermediate container 94ca0bb3f3c9 ---\u0026gt; 0805ddb1719f Successfully built 0805ddb1719f Successfully tagged jvanderaa/network_automation:2.0-rc2 If you have followed along, congratulations, you have created your first Docker Image. This is what will be used to create additional containers, which are a copy of the image, but a whole separate container.\nBecause of the tags, I prefer to use the Makefile to execute my containers as well. Now I just go to the command line within the container by issuing make cli command. This will then take me to the root user prompt of my conatiner.\n1 2 3 4 5 6 7 8 9 10 11 12 $ make cli docker run -it \\ -v /Users/joshv/projects/docker_test:/local \\ -w /local \\ jvanderaa/network_automation:2.0-rc2 bash root@58a4ea071203:/local# ansible-galaxy collection list # /root/.ansible/collections/ansible_collections Collection Version ----------------- ------- ansible.netcommon 1.0.0 cisco.asa 1.0.0 Inside of the container I execute the command ansible-galaxy collection list. This now shows me that there are two collections installed:\nansible.netcommon cisco.asa Summary #With the container built, and a Dockerfile in place. I can now upload the Dockerfile along with the rest of the project file to Git. This coupled with the Makefile will help to build any system quickly. No more trying to find a place to host the Docker image (like Docker Hub), how to install into the proper Python executable any modules that may be needed (like pandevice for PANOS modules) or other SDKs that are helpers to the Ansible Collections being created.\nHopefully this has been helpful. Take a look at the links!\nThanks,\n-Josh\n","date":"2020-06-27","permalink":"https://josh-v.com/docker_for_automation_environment_ansible_210/","section":"Posts","summary":"\u003cp\u003eDocker is a terrific solution for making a consistent working environment. It\u0026rsquo;s been about a year or\nso since I built my very first own Docker container. I had always known why you use a container, but\nwas always intimidated too much so to even get started. I am glad that I did get started and am off\non my journey of using Docker containers. Let me jump into the problem and why? Couple the recent\nexperiences with Docker, and the upcoming move to slim down Ansible and install Collections for\nmost Network Automation modules, I thought it would be a good thing to get a write up done.\u003c/p\u003e","title":"Docker for Automation Environment - Ansible 2.10"},{"content":"Today I\u0026rsquo;m going to walk through the newest part of my personal workflow for working with projects. Straight to the point, this is going to be using Apple Automator to quickly open your project that you wish to work on within VS Code, and presumably PyCharm as well.\nProblem #So what is the problem that I\u0026rsquo;m trying to solve? I am one that generally likes the workspace concept within VS Code, but I don\u0026rsquo;t like having to maintain workspace files. I have found them a little difficult to maintain and keep organized. To that end I have found that there is an option to install VS Code shortcut into your OS path from the command pallet (cmd-P), and path.\nOnce this is installed, you can issue at a terminal (or iTerm2) prompt the command code . and this will bring up VS Code from the folder that you are currently working on.\nThis sounds great, what is the problem? Well, I tend to open a lot of iTerm2 tabs just to open up and go into VS Code. While working in VS Code, I then use the terminal that is baked into VS Code as my terminal. So I have a window that is open unnecessarily.\nMy Solution - Apple Script and Automator #So I was reminded about Apple Automator for some particular reason and I thought this would be a great solution to opening VS Code directly to my folder that I want to work on and be able to close VS Code windows whenever I was done working in a folder. So for me I use a specific directory on my Mac to have all of my projects in them. This is a typical directory structure:\nprojects ├── project1 ├── project2 Automator Flow #The flow is going to have a prompt come to the top when a command shortcut is executed. So -\u0026gt;:\ncommand-shortcut -\u0026gt; Select project -\u0026gt; Open VS Code Start a New Quick Action #When creating a new document in your Automator, make sure to select the type as Quick Action. This type is needed in order to use the Keyboard shortcut later.\nFirst Step: Run AppleScript #I looked at a couple of options here to get the prompt to display. From what I could tell there was options in both AppleScript and JavaScript. I choose to stick with AppleScript in the current iteration for ease of access to the file system. I will have to do additional research, but that would be for a learning thing, not necessarily for productivity gains. I worked through a few links and eventually came up with the following AppleScript:\non run {input, parameters} # Define the folder that is being used as project directory. This is the only thing that needs to be set set projectFolder to \u0026#34;/Users/joshv/projects/\u0026#34; # Use the finder application to get the list of all the folders inside of the folder tell application \u0026#34;Finder\u0026#34; set fileList to get name of folders of folder (projectFolder as POSIX file) end tell # Create a prompt choose from list fileList with prompt \u0026#34;Which project?\u0026#34; # Create a string of projectFolder with result of response set resultString to projectFolder \u0026amp; result as string # Return the path return the resultString as string end run The only parameter that needs to be changed is the path to the directory on the third line, to match what is your own project directory.\nSecond Step: Run Shell Script #I then took the easy way out at the moment to execute an application. I will have to work to add this all into one AppleScript or JavaScript execution in the future, but for now I know this works. It is a short command, first I needed to find out where the code application that was mentioned above is stored. So I did the command which code to get my path to the code shortcut. The final result for me is:\n/usr/local/bin/code $1 This takes an argument that is passed in from the AppleScript and passes it into the shell script. The next change I needed to do on the Run Shell Script module of Automator was to set Pass input to as arguments to pass it in as an argument.\nThis now looks like the following from an Automator application:\nKeyboard Shortcuts #Last thing to do is to assign a keyboard shortcut to your automation.\nOpen Keyboard preferences Select Shortcuts Select Services on the left Scroll to the section General Find the name of your document that you created in Automator Assign a keyboard shortcut, I\u0026rsquo;m using CMD-Shift-' for mine Now when I select the keyboard shortcut, I get a visual prompt of the folders in the project folder specified. And when I select the project and OK I am then taken to either a new VS Code window based in that directory or the already opened VS Code window for that project folder.\nSummary #Not all automation tools have to be Python, Ansible, or other modern language. You can use tools that are provided that may feel old to help you in your every day work. Hopefully this comes in handy for you as well! Let me know. In the end, if this helps great, otherwise this is good documentation for myself if needed in the future!\nThanks,\n-Josh\n","date":"2020-06-21","permalink":"https://josh-v.com/apple_automator/","section":"Posts","summary":"\u003cp\u003eToday I\u0026rsquo;m going to walk through the newest part of my personal workflow for working with projects.\nStraight to the point, this is going to be using Apple Automator to quickly open your project that\nyou wish to work on within VS Code, and presumably PyCharm as well.\u003c/p\u003e\n\u003ch2 id=\"problem\" class=\"relative group\"\u003eProblem \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#problem\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eSo what is the problem that I\u0026rsquo;m trying to solve? I am one that generally likes the workspace concept\nwithin VS Code, but I don\u0026rsquo;t like having to maintain workspace files. I have found them a little\ndifficult to maintain and keep organized. To that end I have found that there is an option to\ninstall VS Code shortcut into your OS path from the command pallet (cmd-P), and \u003ccode\u003epath\u003c/code\u003e.\u003c/p\u003e","title":"Using Apple Automator to Open Projects"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/vscode/","section":"Tags","summary":"","title":"Vscode"},{"content":"In this post I will be taking a look at some of the usability setup of managing Cisco IOS devices with the Ansible Cisco IOS User Module. This can be very helpful for setting up managed user accounts on systems, or the backup user accounts when you have TACACS or RADIUS setup.\nThe module documentation overall looks complete from what I have done for user account management on devices in the past. There are a couple of interesting parameters available, that I may not get to completely on this post. There is support for aggregate, meaning that you can generate the configuration for multiple user accounts and pass it in as one. You can set a password in clear text that gets encrypted when on the device, or you can set a hashed_password with the type of hash and its corresponding value. And as expected with a module for setting user accounts you can also set the privilege level for which the user account uses.\nSSH Before Setting Up SSH Keys #You have probably seen this before, but for completeness sake I did get the output of the SSH login banner. This has the default lab setup on the device. So we do get a banner, but I\u0026rsquo;m getting prompted for a Password as well.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ssh rtr-1 The authenticity of host \u0026#39;rtr-1 (10.250.0.167)\u0026#39; can\u0026#39;t be established. RSA key fingerprint is SHA256:iyEgRBFlLhkW+Z2OOYWPvrjuzhTVY9wULmoHkWYgbrw. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added \u0026#39;rtr-1\u0026#39; (RSA) to the list of known hosts. Warning: the RSA host key for \u0026#39;rtr-1\u0026#39; differs from the key for the IP address \u0026#39;10.250.0.167\u0026#39; Offending key for IP in /Users/joshv/.ssh/known_hosts:170 Are you sure you want to continue connecting (yes/no)? yes ************************************************************************** * IOSv is strictly limited to use for evaluation, demonstration and IOS * * education. IOSv is provided as-is and is not supported by Cisco\u0026#39;s * * Technical Advisory Center. Any use or disclosure, in whole or in part, * * of the IOSv Software or Documentation to any third party for any * * purposes is expressly prohibited except as otherwise authorized by * * Cisco in writing. * **************************************************************************Password: Adding SSH Key Users #Copying from the example on the module definition, I went ahead and created a playbook that will create an account on the same device but with my local computer account. Here is the playbook:\n--- - name: \u0026#34;PLAY 1: WORKING WITH IOS USER MODULE\u0026#34; hosts: cisco_routers connection: network_cli tasks: - name: \u0026#34;TASK 1: Add local username with SSH Key\u0026#34; ios_user: name: joshv nopassword: True sshkey: \u0026#34;{{ lookup(\u0026#39;file\u0026#39;, \u0026#39;~/.ssh/id_rsa.pub\u0026#39;) }}\u0026#34; state: absent - name: \u0026#34;FINAL TASK: Save Config\u0026#34; ios_config: save_when: always It is a single play playbook, with 2 tasks. Task 1 will add the local id_rsa public key to the IOS device. The final task is a play to save the configuration.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ansible-playbook working_with_ios_user-1.yml PLAY [PLAY 1: WORKING WITH IOS USER MODULE] **************************************************** TASK [TASK 1: Add local username with SSH Key] ************************************************* [WARNING]: Module did not set no_log for update_password [WARNING]: Module did not set no_log for password_type changed: [r1] TASK [debug] *********************************************************************************** ok: [r1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;ansible_facts\u0026#34;: { \u0026#34;discovered_interpreter_python\u0026#34;: \u0026#34;/usr/bin/python\u0026#34; }, \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;ip ssh pubkey-chain\u0026#34;, \u0026#34;username joshv\u0026#34;, \u0026#34;key-hash ssh-rsa \u0026lt;hash_masked\u0026gt; joshv@\u0026lt;adevice\u0026gt;\u0026#34;, \u0026#34;exit\u0026#34;, \u0026#34;exit\u0026#34;, \u0026#34;username joshv nopassword\u0026#34; ], \u0026#34;failed\u0026#34;: false, \u0026#34;warnings\u0026#34;: [ \u0026#34;Module did not set no_log for update_password\u0026#34;, \u0026#34;Module did not set no_log for password_type\u0026#34; ] } } TASK [FINAL TASK: Save Config] ***************************************************************** changed: [r1] PLAY RECAP ************************************************************************************* r1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 On execution one can see that the commands pushed in the debug task including setting up an IP SSH keypair, setting a username of joshv, and setting the key hash. Then Ansible exits to what is expected to be the first level of config mode and sets username joshv without a password.\nExecution is pretty much what we would expect of adding a username to the device. Taking a look at if we get prompted when connecting to the device is a no, I do not.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ ssh rtr-1 ************************************************************************** * IOSv is strictly limited to use for evaluation, demonstration and IOS * * education. IOSv is provided as-is and is not supported by Cisco\u0026#39;s * * Technical Advisory Center. Any use or disclosure, in whole or in part, * * of the IOSv Software or Documentation to any third party for any * * purposes is expressly prohibited except as otherwise authorized by * * Cisco in writing. * ************************************************************************** ************************************************************************** * IOSv is strictly limited to use for evaluation, demonstration and IOS * * education. IOSv is provided as-is and is not supported by Cisco\u0026#39;s * * Technical Advisory Center. Any use or disclosure, in whole or in part, * * of the IOSv Software or Documentation to any third party for any * * purposes is expressly prohibited except as otherwise authorized by * * Cisco in writing. * ************************************************************************** rtr-1# Taking a look at the configuration in the router, it looks exactly as we would expect. There are only two users configured. The first being the one that Ansible uses to connect to this device. The second being the one we just reconfigured.\n1 2 3 4 rtr-1#show run | i username username cisco secret 5 $1$GNTQ$RpNy.E9LZMzgrOz/g2pYJ. username joshv nopassword username joshv On the output you see that there is the username joshv multiple times. One is in the generic username section that was created with the command username joshv nopassword and then another time that is within the public key section of the SSH configuration.\nRemoving SSH Key User #To go along with creating an user on the device, I have created the playbook to remove the same user from the device. This is as simple as changing the state from present to absent. This will remove all of what was created on the device.\n--- - name: \u0026#34;PLAY 1: WORKING WITH IOS USER MODULE\u0026#34; hosts: cisco_routers connection: network_cli tasks: - name: \u0026#34;TASK 1: Remove local username with SSH Key\u0026#34; ios_user: name: joshv nopassword: True sshkey: \u0026#34;{{ lookup(\u0026#39;file\u0026#39;, \u0026#39;~/.ssh/id_rsa.pub\u0026#39;) }}\u0026#34; state: absent register: config_output - debug: msg: \u0026#34;{{ config_output }}\u0026#34; - name: \u0026#34;FINAL TASK: Save Config\u0026#34; ios_config: save_when: always Execution looks extremely similar. Here it is:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 PLAY [PLAY 1: WORKING WITH IOS USER MODULE] **************************************************** TASK [TASK 1: Remove local username with SSH Key] ********************************************** [WARNING]: Module did not set no_log for update_password [WARNING]: Module did not set no_log for password_type changed: [r1] TASK [debug] *********************************************************************************** ok: [r1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;ansible_facts\u0026#34;: { \u0026#34;discovered_interpreter_python\u0026#34;: \u0026#34;/usr/bin/python\u0026#34; }, \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;ip ssh pubkey-chain\u0026#34;, \u0026#34;no username joshv\u0026#34;, \u0026#34;exit\u0026#34; ], \u0026#34;failed\u0026#34;: false, \u0026#34;warnings\u0026#34;: [ \u0026#34;Module did not set no_log for update_password\u0026#34;, \u0026#34;Module did not set no_log for password_type\u0026#34; ] } } TASK [FINAL TASK: Save Config] ***************************************************************** changed: [r1] PLAY RECAP ************************************************************************************* r1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 In writing of this I did find what I would consider a bug within Ansilbe\u0026rsquo;s ios_user. If you use an SSH Key with the credential, you will need to remove the user account with running the same taskk 2 times. This is filed under issue https://github.com/ansible/ansible/issues/68238\nExecuting the module a second time you get the full removal of the user account.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 PLAY [PLAY 1: WORKING WITH IOS USER MODULE] **************************************************** TASK [TASK 1: Remove local username with SSH Key] ********************************************** [WARNING]: Module did not set no_log for update_password [WARNING]: Module did not set no_log for password_type changed: [r1] TASK [debug] *********************************************************************************** ok: [r1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;ansible_facts\u0026#34;: { \u0026#34;discovered_interpreter_python\u0026#34;: \u0026#34;/usr/bin/python\u0026#34; }, \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ { \u0026#34;answer\u0026#34;: \u0026#34;y\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;no username joshv\u0026#34;, \u0026#34;newline\u0026#34;: false, \u0026#34;prompt\u0026#34;: \u0026#34;This operation will remove all username related configurations with same name\u0026#34; } ], \u0026#34;failed\u0026#34;: false, \u0026#34;warnings\u0026#34;: [ \u0026#34;Module did not set no_log for update_password\u0026#34;, \u0026#34;Module did not set no_log for password_type\u0026#34; ] } } TASK [FINAL TASK: Save Config] ***************************************************************** changed: [r1] PLAY RECAP ************************************************************************************* r1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Setting Username and Password - No Key #Now that I have gone through the use of creating a SSH Key user, let\u0026rsquo;s take a look at setting an user account on the device with a credential. I\u0026rsquo;ve created a local environmental variable named NEW_PASSWORD that has the credential that I wish to set the username to. This could be any lookup that gets a password, such as a lookup to a password manager.\n--- - name: \u0026#34;PLAY 1: WORKING WITH IOS USER MODULE\u0026#34; hosts: cisco_routers connection: network_cli tasks: - name: \u0026#34;TASK 1: Add local username with SSH Key\u0026#34; ios_user: name: josh2 configured_password: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;, \u0026#39;NEW_PASSWORD\u0026#39;) }}\u0026#34; state: present privilege: 15 register: config_output - debug: msg: \u0026#34;{{ config_output }}\u0026#34; - name: \u0026#34;FINAL TASK: Save Config\u0026#34; ios_config: save_when: always The output on this particular setup is not idempotent. Each time the play will be run it will set a new username and password on the device due to the checking of the running configuration. You will need to add some additional logic to your playbook to have the task only executed when a condition is met.\nHere is the execution. Note that Ansible masks the password being set.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 PLAY [PLAY 1: WORKING WITH IOS USER MODULE] **************************************************** TASK [TASK 1: Add local username with SSH Key] ************************************************* [WARNING]: Module did not set no_log for update_password [WARNING]: Module did not set no_log for password_type changed: [r1] TASK [debug] *********************************************************************************** ok: [r1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;ansible_facts\u0026#34;: { \u0026#34;discovered_interpreter_python\u0026#34;: \u0026#34;/usr/bin/python\u0026#34; }, \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;username josh2 secret ********\u0026#34; ], \u0026#34;failed\u0026#34;: false, \u0026#34;warnings\u0026#34;: [ \u0026#34;Module did not set no_log for update_password\u0026#34;, \u0026#34;Module did not set no_log for password_type\u0026#34; ] } } TASK [FINAL TASK: Save Config] ***************************************************************** changed: [r1] PLAY RECAP ************************************************************************************* r1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Summary #From the examples that I have given, hopefully this will help to see what you could do in your own environment. Need to regularly rotate an offline access password? A playbook may be a way that is low impact to get you on your way for automating the management of your Cisco IOS devices.\nI also started with the use of SSH keys as well as this may be an under utilized method to log into devices. This sets up and uses a known cryptographic key set for authentication. Please check with the team/individuals responsible for security before implementing.\nAs always, I hope this has helped!\nI\u0026rsquo;ve added the Playbooks executed within this post to my collection of examples on Github at https://github.com/jvanderaa/ansible-using_ios.\n","date":"2020-03-14","permalink":"https://josh-v.com/ansible-cisco-ios-user/","section":"Posts","summary":"\u003cp\u003eIn this post I will be taking a look at some of the usability setup of managing Cisco IOS devices\nwith the \u003ca href=\"https://docs.ansible.com/ansible/latest/modules/ios_config_module.html\" target=\"_blank\" rel=\"noreferrer\"\u003eAnsible Cisco IOS User Module\u003c/a\u003e.\nThis can be very helpful for setting up managed user accounts on systems, or the backup user\naccounts when you have TACACS or RADIUS setup.\u003c/p\u003e\n\u003cp\u003eThe module documentation overall looks complete from what I have done for user account management on\ndevices in the past. There are a couple of interesting parameters available, that I may not get to\ncompletely on this post. There is support for aggregate, meaning that you can generate the\nconfiguration for multiple user accounts and pass it in as one. You can set a password in clear text\nthat gets encrypted when on the device, or you can set a hashed_password with the type of hash and\nits corresponding value. And as expected with a module for setting user accounts you can also set\nthe privilege level for which the user account uses.\u003c/p\u003e","title":"Ansible Cisco IOS User Module"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/ios_user/","section":"Tags","summary":"","title":"Ios_user"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/network-automation/","section":"Tags","summary":"","title":"Network Automation"},{"content":"This has become a post about the ios_interfaces module with documentation that can be found Ansible ios_interfaces doc. Originally I was going to write about the deprecations for just the Cisco IOS modules. Then as I investigated further, I had found that there are many more modules that are being deprecated. In this post I will take a closer look at the differences between the ios_interface and ios_vlan modules that I had written posts on last year and what their new counter parts look like. And in the end the post had quite a bit of good detail about the module. I think you will like what is here.\nPrevious Posts\nios_interface ios_vlan Module Deprecations #In addition to the Cisco \u0026ldquo;legacy\u0026rdquo; interface and vlan modules that are being deprecated, the Ansible generic modules are being deprecated as well. These include net_interface, net_linkagg, net_l2/l3_interface, and net_l2/l3_vlan.\nNot to be outdone, not only are the Cisco and Ansible generic modules being deprecated, so are each of the same set of modules for Juniper, EOS, VYOS, NXOS, IOSXR, and Netvisor.\nIn all by doing a browser search for (D) on the Ansible 2.9 Network Modules page I come across a total of 77 different modules that are in the process of being deprecated. That is quite a bit, so make sure that you are taking a look at your playbooks to look for this.\nOne of the downsides I see coming out of this module change is the change from ios_interface to ios_interfaces. This is such a subtle difference between the two. You are going to need to pay extra attention to it. In fact I was taken back when I first saw the parameters of the module being posted on the Network To Code Public Slack that I had to take a second look. I thought that someone was way off on the parameters they were using. Then I saw the deprecations and new modules.\nPANOS Module Deprecation #I did also observe an interesting (in my mind) planned deprecation. All of the PANOS modules are planned to be deprecated in favor of using community drivers from Ansible Galaxy. Why is this interesting? Well, this is the start of the move to move modules out of Ansible Core and start using Ansible Galaxy to distribute the modules that you need. More on that to come in another post.\nDifferences #There are quite a bit of differences in the modules. The first level parameters for *_interfaces across vendors has been reduced to just two, config and state.\nA second difference that I\u0026rsquo;m observing is the lack of the ability to save the config when change. This means that you as the playbook creator will need to take action such as notifying a handler or running a save config when there is a change. You can do this, or have a task executed when there is a change.\nConfig #All of the bulk of the configuration has moved into the config parameter. Within Cisco ios_interfaces you now have the options to configure the description, duplex, enabled, mtu, name*, and speed. These were previously first level parameters within the module definition. You can find the module definition at Ansible Docs ios_interfaces.\nState #The state is now referencing the configuration of the interface. It does not state whether or not the interface is enabled or disabled. That is controlled by the enabled sub-parameter of config. The options for state include merged (default setting), replaced, overridden, and deleted.\nState: Merged #This looks to take whatever is already in the interface configuration and adding/replacing based on what the module parameters that are configured. So if you have an interface that has just the speed configured and you just have a task that configures the duplex, you will have speed and duplex configured.\nWith the merged module, you will run the commands that are defined, not worrying about the defaults. For this demo, I have set the MTU to 1450, which is different than the default of 1500 for this device type. You will see that in other states, that Ansible will change the configuration. Where as with the merged type, you will run the commands seen in the module parameters, not worrying about the other parameters not provided.\ntasks: - name: \u0026#34;TASK 1: IOS \u0026gt;\u0026gt; Set some interfaces with merge\u0026#34; ios_interfaces: state: replaced config: - name: GigabitEthernet0/3 enabled: yes description: \u0026#34;Configured by Ansible\u0026#34; register: ios_interface_output - name: \u0026#34;TASK 2: SYS \u0026gt;\u0026gt; DEBUG OUTPUT\u0026#34; debug: msg: \u0026#34;{{ ios_interface_output }}\u0026#34; 1 2 3 \u0026#34;commands\u0026#34;: [ \u0026#34;interface GigabitEthernet0/3\u0026#34;, \u0026#34;description Configured by Ansible\u0026#34; State: Replaced #When using replaced, the entire interface will be configured with what is set in the module. If you set duplex only, you will only get the duplex of the interface set.\nIn this example I will only be looking to enable the interface and change the description. There will be no changes to the other interfaces defined.\ntasks: - name: \u0026#34;TASK 1: IOS \u0026gt;\u0026gt; Set some interfaces with merge\u0026#34; ios_interfaces: state: replaced config: - name: GigabitEthernet0/3 enabled: yes description: \u0026#34;Configured by Ansible\u0026#34; register: ios_interface_output - name: \u0026#34;TASK 2: SYS \u0026gt;\u0026gt; DEBUG OUTPUT\u0026#34; debug: msg: \u0026#34;{{ ios_interface_output }}\u0026#34; The output (shown in full here only, will skip the \u0026ldquo;after\u0026rdquo; and \u0026ldquo;before\u0026rdquo; keys in subsequent examples) shows that the only changes being made are to GigabitEthernet0/3. This is similar to what we will see in the overridden section. Overridden will configure every interface on the device vs replaced looks to only be handling the interface defined within the config parameter.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 \u0026#34;msg\u0026#34;: { \u0026#34;after\u0026#34;: [ { \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;loopback0\u0026#34; }, { \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/0\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; }, { \u0026#34;description\u0026#34;: \u0026#34;MANAGEMENT\u0026#34;, \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/1\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; }, { \u0026#34;description\u0026#34;: \u0026#34;PRODUCTION\u0026#34;, \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;mtu\u0026#34;: 1400, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/2\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; }, { \u0026#34;description\u0026#34;: \u0026#34;Configured by Ansible\u0026#34;, \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/3\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; } ], \u0026#34;before\u0026#34;: [ { \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;loopback0\u0026#34; }, { \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/0\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; }, { \u0026#34;description\u0026#34;: \u0026#34;MANAGEMENT\u0026#34;, \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/1\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; }, { \u0026#34;description\u0026#34;: \u0026#34;PRODUCTION\u0026#34;, \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;mtu\u0026#34;: 1400, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/2\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; }, { \u0026#34;description\u0026#34;: \u0026#34;BEFORE ANSIBLE\u0026#34;, \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;mtu\u0026#34;: 1450, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/3\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; } ], \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;interface GigabitEthernet0/3\u0026#34;, \u0026#34;no mtu\u0026#34;, \u0026#34;description Configured by Ansible\u0026#34; State: Overridden #This one was not completely obvious to me when originally looking at. But as I tested, I have now come to find that this state is something to be VERY cautious with. In the testing, I had the following configuration on the devices:\n1 2 3 4 5 6 interface GigabitEthernet0/1 description MANAGEMENT OUTPUT OMITTED ! interface GigabitEthernet0/2 description PRODUCTION You can see that there are configurations applied to the description. I then created these playbook tasks to test the Overridden setting.\ntasks: - name: \u0026#34;TASK 1: IOS \u0026gt;\u0026gt; Set some interfaces with merge\u0026#34; ios_interfaces: state: overridden config: - name: GigabitEthernet0/3 enabled: yes description: \u0026#34;Configured by Ansible\u0026#34; register: ios_interface_output - name: \u0026#34;TASK 2: SYS \u0026gt;\u0026gt; DEBUG OUTPUT\u0026#34; debug: msg: \u0026#34;{{ ios_interface_output }}\u0026#34; The output shows that Ansible is going to erase the description lines:\n1 2 3 4 5 \u0026#34;commands\u0026#34;: [ \u0026#34;interface GigabitEthernet0/1\u0026#34;, \u0026#34;no description\u0026#34;, \u0026#34;interface GigabitEthernet0/2\u0026#34;, \u0026#34;no description\u0026#34; This indicates that Ansible will default settings that are not specifically defined within the module.\nThe next test I changed the MTU on GigabitEthernet0/1 and GigabitEthernet0/2 to some random 14xx MTUs. I\u0026rsquo;ve left everything else the same as the play above with no changing fo the MTU, just setting the description on a single interface. The results from running that playbook now show that the interface MTU is reset to default since it was not statically defined in the task.\n1 2 3 4 5 6 7 8 \u0026#34;commands\u0026#34;: [ \u0026#34;interface GigabitEthernet0/1\u0026#34;, \u0026#34;no mtu\u0026#34;, \u0026#34;interface GigabitEthernet0/2\u0026#34;, \u0026#34;no mtu\u0026#34;, \u0026#34;interface GigabitEthernet0/3\u0026#34;, \u0026#34;no mtu\u0026#34; ], State: Overridden - Loop #So what does this look like if we wanted to loop over a set of interfaces? Would there be special considerations made for the module? The answer is no. If you attempted to loop over a module that is using the state of overridden, then you are going to default the other interfaces. Given the following task:\ntasks: - name: \u0026#34;TASK 1: IOS \u0026gt;\u0026gt; Set some interfaces with merge\u0026#34; ios_interfaces: state: overridden config: - name: \u0026#34;{{ item }}\u0026#34; enabled: yes description: \u0026#34;Configured by Ansible\u0026#34; register: ios_interface_output loop: - \u0026#34;GigabitEthernet0/2\u0026#34; - \u0026#34;GigabitEthernet0/3\u0026#34; loop_control: loop_var: item - name: \u0026#34;TASK 2: SYS \u0026gt;\u0026gt; DEBUG OUTPUT\u0026#34; debug: msg: \u0026#34;{{ ios_interface_output }}\u0026#34; You will have two loops. The first time through the loop when the loop_var is GigabitEthernet0/2 you will have the other interfaces all defaulted, except GigabitEthernet0/2 will have the state enabled and the description set to Configured by Ansible. The rest of the interface descriptions will be removed. MTU all set to default, and so on.\nThe second time through the loop the module will configure GigabitEthernet0/3 with each of the interface configurations. Defaulting the rest of the interfaces on teh device. So the description at the end of this execution for GigabitEthernet0/2 will be blank. Even though it was configured on the first loop through.\nTo handle this you will need to define the interfaces within the context of the config. Here is the new configuration of the tasks:\ntasks: - name: \u0026#34;TASK 1: IOS \u0026gt;\u0026gt; Set some interfaces with merge\u0026#34; ios_interfaces: state: overridden config: - name: \u0026#34;GigabitEthernet0/2\u0026#34; enabled: yes description: \u0026#34;Configured by Ansible\u0026#34; - name: \u0026#34;GigabitEthernet0/3\u0026#34; enabled: yes description: \u0026#34;Configured by Ansible\u0026#34; register: ios_interface_output - name: \u0026#34;TASK 2: SYS \u0026gt;\u0026gt; DEBUG OUTPUT\u0026#34; debug: msg: \u0026#34;{{ ios_interface_output }}\u0026#34; With having multiple interfaces defined under the config as another list item you are able to get both of the interfaces configured with what you are looking to do.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 \u0026#34;after\u0026#34;: [ { \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;loopback0\u0026#34; }, { \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/0\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; }, { \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/1\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; }, { \u0026#34;description\u0026#34;: \u0026#34;Configured by Ansible\u0026#34;, \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/2\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; }, { \u0026#34;description\u0026#34;: \u0026#34;Configured by Ansible\u0026#34;, \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/3\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; } ], \u0026#34;before\u0026#34;: [ { \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;loopback0\u0026#34; }, { \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/0\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; }, { \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/1\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; }, { \u0026#34;description\u0026#34;: \u0026#34;BEFORE ANSIBLE\u0026#34;, \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/2\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; }, { \u0026#34;description\u0026#34;: \u0026#34;BEFORE ANSIBLE\u0026#34;, \u0026#34;duplex\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;enabled\u0026#34;: true, \u0026#34;name\u0026#34;: \u0026#34;GigabitEthernet0/3\u0026#34;, \u0026#34;speed\u0026#34;: \u0026#34;auto\u0026#34; } ], \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;interface GigabitEthernet0/2\u0026#34;, \u0026#34;description Configured by Ansible\u0026#34;, \u0026#34;interface GigabitEthernet0/3\u0026#34;, \u0026#34;description Configured by Ansible\u0026#34; You now see within the commands key that both of hte interfaces are configured by Ansible. At this point with both interfaces being defined in the config parameter, you get both of the interfaces configured. To do this more programmatically you would need to create the list ahead of time and feed the list of interfaces with their state into the module. This may be a future blog post.\nState: Deleted #The task looks straight to the point for the deleted status. As expected. In this you define which interface name is to have the configuration removed, and then the module will default all of the sub-parameters of description, duplex, enabled, mtu, and speed. Note that the enabled default in the module is currently (2020-01-26) set to enabled.\ntasks: - name: \u0026#34;TASK 1: IOS \u0026gt;\u0026gt; Set some interfaces with merge\u0026#34; ios_interfaces: state: deleted config: - name: GigabitEthernet0/3 register: ios_interface_output - name: \u0026#34;TASK 2: SYS \u0026gt;\u0026gt; DEBUG OUTPUT\u0026#34; debug: msg: \u0026#34;{{ ios_interface_output }}\u0026#34; When the configuration previously had an interface description and an MTU set, the following is the commands that are executed on just the single interface that is defined in the task:\n1 2 3 4 \u0026#34;commands\u0026#34;: [ \u0026#34;interface GigabitEthernet0/3\u0026#34;, \u0026#34;no description\u0026#34;, \u0026#34;no mtu\u0026#34; ","date":"2020-01-26","permalink":"https://josh-v.com/ansible-cisco-ios-interfaces-module/","section":"Posts","summary":"\u003cp\u003eThis has become a post about the \u003cstrong\u003eios_interfaces\u003c/strong\u003e module with documentation that can be found\n\u003ca href=\"https://docs.ansible.com/ansible/latest/modules/ios_interfaces_module.html\" target=\"_blank\" rel=\"noreferrer\"\u003eAnsible ios_interfaces doc\u003c/a\u003e.\nOriginally I was going to write about the deprecations for just the Cisco IOS modules. Then as I\ninvestigated further, I had found that there are many more modules that are being deprecated. In\nthis post I will take a closer look at the differences between the \u003ccode\u003eios_interface\u003c/code\u003e and \u003ccode\u003eios_vlan\u003c/code\u003e\nmodules that I had written posts on last year and what their new counter parts look like. And in the\nend the post had quite a bit of good detail about the module. I think you will like what is here.\u003c/p\u003e","title":"Ansible Cisco ios_interfaces module"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/deprecation/","section":"Tags","summary":"","title":"Deprecation"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/disney/","section":"Tags","summary":"","title":"Disney"},{"content":"This will be a brief departure from the automation focused attention that I have been giving to this blog over the past year or so. This week in the United States was the launch of Disney+ streaming service. I have subscribed to it at this point and have found some interesting data based on SNMP polling my network.\nThis post is about the bandwidth that I am seeing used, not about anything about the service, or if another service is better. I don\u0026rsquo;t have the time for that at this time. This is just about what was an unexpected jump in the bandwidth usage with the new application. But I am very much OK with that as my subscription level is taking care of that.\nStreaming Setup #So what this is going to show is numbers with just a single device. So this isn\u0026rsquo;t a full across the board deep test. But it is something to get some numbers out there. My household streaming consists of primarily an Apple TV Full HD (not the 4k one) or an iPad mini streaming.\nMy broadband provider is a cable service provider that speed tests have shown consistent speeds at around 200 Mbps down, 11 Mbps up.\nStreaming Bandwidth Number - Historically #Historically I\u0026rsquo;ve always maintained that based on a Netflix stream, or use of PlayStation Vue that an HD stream would use somewhere between 3-5 Mbps of bandwidth on a broadband network. I\u0026rsquo;ll show where that still remains true on the bandwidth graph. That has held true so far.\nDisney+ Streaming #So what have I seen at this point from just 2 days of use of Disney+? It uses much more bandwidth than I originally expected, in fact, it has been over double that of Netflix streams. So much more so that the 95th percentile on the home network with just a single stream running has consistently run up to 11 Mbps for my download.\nQuality? #From the picture that I have seen, it does look very crisp and clear. Makes sense that there are more bits coming across the wire. I would say that at least in my experience there is a correlation of quality to the amount of bits coming across.\nThe Netflix subscription is the basic HD level. I have not done that study of what that actually means if that is 720p or 1080p that is coming across. I also do not know what compression there is within the applications.\nGraph To Show #Here is the bandwidth graphs. I will try to pretty up the graphics a little more in a future post.\nThe two spikes up to the red line (95th percentile) on the top side of 0 Mbps are the two times that Disney+ was in use. Definitely some good amount of usage there. If you see the other couple of early morning spikes, that is Netflix to an iPad in use. There are some other general streams of data that happen throughout the day as the family jumps in and out of some other streaming services. But nothing close to the Disney+ numbers.\nSummary #There has been some negative press around the large launch, although I have not had the experiences. Thus far things have been good, we see that there is more bandwidth utilized from Disney+ launch. At this point a kudos to Disney\u0026rsquo;s CDN providers as well that have been able to push out this kind of data rates. I assume that others are likely having the same bandwidth utilization with the service.\nI hope this helps!\nI have it on my radar to move to a more modern network graphing setup as well, I\u0026rsquo;m just not to that point in the home environment yet.\n","date":"2019-11-14","permalink":"https://josh-v.com/disney_plus_streaming/","section":"Posts","summary":"\u003cp\u003eThis will be a brief departure from the automation focused attention that I have been giving to this\nblog over the past year or so. This week in the United States was the launch of Disney+ streaming\nservice. I have subscribed to it at this point and have found some interesting data based on SNMP\npolling my network.\u003c/p\u003e\n\u003cp\u003eThis post is about the bandwidth that I am seeing used, not about anything about the service, or if\nanother service is better. I don\u0026rsquo;t have the time for that at this time. This is just about what was\nan unexpected jump in the bandwidth usage with the new application. But I am very much OK with that\nas my subscription level is taking care of that.\u003c/p\u003e","title":"Disney Plus Streaming Bandwidth"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/homenet/","section":"Tags","summary":"","title":"Homenet"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/streaming/","section":"Tags","summary":"","title":"Streaming"},{"content":"In an earlier post I covered the differences between ios_config and cli_config. However I did not cover what the difference was between ios_command and cli_command. Most of the items covered there remain the same. So this will be a post that mostly gets straight to it and sees what the difference is.\nA reminder that I am also putting playbooks used here out on Github. You can find this at: https://github.com/jvanderaa/ansible-using_ios\nDifferences #First, for the cli_commands module, you must be using a connection method of network_cli. You should not use connection: local for this module. Note that the cli_command can also be used with multiple device types, including multiple vendors. Take a look at the cli_command documentation page that there is a link at the bottom of the post.\nParameters #As in the config modules, the first difference is how you pass what you wish to have executed. With cli_command you are sending a single string, just one command. This is under the command parameter. With ios_command you get to send a list of commands send with the commands parameter. This can be handy in some times to execute a whole bunch of commands in one task to a device.\nOutput #The second major difference according to the documentation between cli_command and ios_command is the return format. Assuming a single command on the ios_command side of things is sent, here are the returns from the module:\nios_command returns # failed_conditions stdout stdout_lines cli_command returns # json stdout Analysis of returns #The first thing about the stdout_lines output is that it makes it very human readable what the output of the command is. If you are working on something programmatically speaking, you will likely only want to use stdout.\nNext we see that cli_command has a json return, which is going to provide more structured feedback from the command.\nBoth have in common the stdout return, however, the data type is very different. Since cli_command sent only a single string, the return is a single string. On ios_command this is a list of responses. Even if you sent a single command, it comes back as a list that is only one item. So you will need to access variable_name.stdout.0 (or variable_name.stdout[0]) to get at the command output.\nLet\u0026rsquo;s get to taking a look at the output.\nDemo of commands #Let\u0026rsquo;s take a look at how the responses look with just a single command first. I have a preference of taking a look at NTP associations lately.\nios_command - single command #Here is what the task portion of the playbook looks like with a single command.\n1 2 3 4 5 6 7 8 9 10 tasks: - name: \u0026#34;TASK 1: Get NTP Associations\u0026#34; ios_command: commands: - show ntp associations register: command_output - name: \u0026#34;TASK 2: Debug output\u0026#34; debug: msg: \u0026#34;{{ command_output }}\u0026#34; }\nThe output from this is as follows assuming an NTP association to the cloudflare NTP servers:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 PLAY [PLAY 1: Using ios_command for a single command] ********************************************** TASK [TASK 1: Get NTP Associations] **************************************************************** ok: [r1] TASK [TASK 2: Debug output] ************************************************************************ ok: [r1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;failed\u0026#34;: false, \u0026#34;stdout\u0026#34;: [ \u0026#34;address ref clock st when poll reach delay offset disp\\n*~162.159.200.123 10.72.8.95 3 14 64 7 21.111 72.531 0.746\\n * sys.peer, # selected, + candidate, - outlyer, x falseticker, ~ configured\u0026#34; ], \u0026#34;stdout_lines\u0026#34;: [ [ \u0026#34;address ref clock st when poll reach delay offset disp\u0026#34;, \u0026#34;*~162.159.200.123 10.72.8.95 3 14 64 7 21.111 72.531 0.746\u0026#34;, \u0026#34; * sys.peer, # selected, + candidate, - outlyer, x falseticker, ~ configured\u0026#34; ] ] } } PLAY RECAP ************************************************************************************************************ r1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 There are four items returned, with the first two primarily being \u0026ldquo;standard\u0026rdquo; Ansible returns for changed and failed. There is then:\nstdout: List of outputs, so when there are multiple commands. stdout_lines: List of lists, the inner list is the commands printed line by line, which makes it more human readable. The outer list is like that of stdout, that is for each command run, including if there is only a single command. Look at the end of line 11, and line 13. This shows that there is in fact a list [] of responses to parse through. cli_command - single command #The tasks on the cli_command looks pretty similar. However there a few differences. First the value of command: is a string, this you will see by not having a - in the line. I\u0026rsquo;m also going to use quotes around to demonstrate this.\ntasks: - name: \u0026#34;TASK 1: Get NTP Associations\u0026#34; cli_command: command: \u0026#34;show ntp associations\u0026#34; register: command_output - name: \u0026#34;TASK 2: Debug output\u0026#34; debug: msg: \u0026#34;{{ command_output }}\u0026#34; The command output looks awfully similar now in recent versions of Ansible.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 PLAY [PLAY 1: Using cli_command for a single command] **************************************************************************** TASK [TASK 1: Get NTP Associations] ********************************************************************************************** ok: [r1] TASK [TASK 2: Debug output] ****************************************************************************************************** ok: [r1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;failed\u0026#34;: false, \u0026#34;stdout\u0026#34;: \u0026#34;address ref clock st when poll reach delay offset disp\\n*~162.159.200.123 10.72.8.95 3 50 64 377 16.772 28.507 5.117\\n * sys.peer, # selected, + candidate, - outlyer, x falseticker, ~ configured\u0026#34;, \u0026#34;stdout_lines\u0026#34;: [ \u0026#34;address ref clock st when poll reach delay offset disp\u0026#34;, \u0026#34;*~162.159.200.123 10.72.8.95 3 50 64 377 16.772 28.507 5.117\u0026#34;, \u0026#34; * sys.peer, # selected, + candidate, - outlyer, x falseticker, ~ configured\u0026#34; ] } } PLAY RECAP *********************************************************************************************************************** r1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 The big difference here is that the stdout part of the response is of type string, and not of a type list like ios_command. Take a look at line number 11 where stdout is. Immediately following the colon is a double quote, indicating that this is a string. So if you are doing work on this variable, you will need to take string actions.\nios_comamnd - multiple commands #The \u0026ldquo;bonus\u0026rdquo; of the ios_command module is that you can run multiple commands within a single task. As I type that out, it seems against the idea of individual task execution, to do 2 or more things in a single task. But that is what the module allows us in this instance. Let\u0026rsquo;s take a look at this playbook to verify NTP information and then get the time from the device.\nThis could be a part of the ios_command history as well. When ios_command was written each individual task would start a new connection to IOS devices. So to preserve the number of logins required it would be good to be able to execute multiple lines.\nThere is now a single task, but there are two commands in the commands section. These will be run and saved to a variable named command_output.\n- name: \u0026#34;TASK 1: Get NTP Associations\u0026#34; ios_command: commands: - show ntp associations - show clock register: command_output - name: \u0026#34;TASK 2: Debug output\u0026#34; debug: msg: \u0026#34;{{ command_output }}\u0026#34; Let\u0026rsquo;s take a look at how this looks now:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 PLAY [PLAY 1: Using ios_command for a single command] **************************************************************************** TASK [TASK 1: Get NTP Associations] ********************************************************************************************** ok: [r1] TASK [TASK 2: Debug output] ****************************************************************************************************** ok: [r1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;failed\u0026#34;: false, \u0026#34;stdout\u0026#34;: [ \u0026#34;address ref clock st when poll reach delay offset disp\\n*~162.159.200.123 10.72.8.95 3 262 512 377 16.551 65.112 0.097\\n * sys.peer, # selected, + candidate, - outlyer, x falseticker, ~ configured\u0026#34;, \u0026#34;21:57:28.776 UTC Sun Oct 20 2019\u0026#34; ], \u0026#34;stdout_lines\u0026#34;: [ [ \u0026#34;address ref clock st when poll reach delay offset disp\u0026#34;, \u0026#34;*~162.159.200.123 10.72.8.95 3 262 512 377 16.551 65.112 0.097\u0026#34;, \u0026#34; * sys.peer, # selected, + candidate, - outlyer, x falseticker, ~ configured\u0026#34; ], [ \u0026#34;21:57:28.776 UTC Sun Oct 20 2019\u0026#34; ] ] } } PLAY RECAP *********************************************************************************************************************** r1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 This is where you start to see that there are multiple list items in the response. Taking a look at line number 11 we still have the [ of the list showing at the end, then line 12 ends in a comma, indicating the next list item. Line 13 ends the list. This repeats on the stdout_lines as well.\nIf you wanted to get at just the time of the device in this instance, this is how you would do a debug task for it:\n- name: \u0026#34;Debug time\u0026#34; debug: msg: \u0026#34;{{ command_output.stdout[1] }}\u0026#34; You see that you need to call the variable name, then the return value that you are looking for - stdout. Then you need the list position on the response that corresponds to where it was called on the ios_command module.\ncli_command - multiple commands #To do the same multiple commands on the cli_command front, you will want to use a loop. Here I prefer to use the with_items loop. You will see several more key/value pairs on the variable when using a loop, so let\u0026rsquo;s take a look below:\n- name: \u0026#34;TASK 1: Get NTP Associations\u0026#34; cli_command: command: \u0026#34;{{ item }}\u0026#34; register: command_output with_items: - \u0026#34;show ntp associations\u0026#34; - \u0026#34;show clock\u0026#34; - name: \u0026#34;TASK 2: Debug command output\u0026#34; debug: msg: \u0026#34;{{ command_output }}\u0026#34; The output:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 PLAY [PLAY 1: Using cli_command for a single command] **************************************************************************** TASK [TASK 1: Get NTP Associations] ********************************************************************************************** ok: [r1] =\u0026gt; (item=show ntp associations) ok: [r1] =\u0026gt; (item=show clock) TASK [TASK 2: Debug command output] ********************************************************************************************** ok: [r1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;msg\u0026#34;: \u0026#34;All items completed\u0026#34;, \u0026#34;results\u0026#34;: [ { \u0026#34;ansible_loop_var\u0026#34;: \u0026#34;item\u0026#34;, \u0026#34;changed\u0026#34;: false, \u0026#34;failed\u0026#34;: false, \u0026#34;invocation\u0026#34;: { \u0026#34;module_args\u0026#34;: { \u0026#34;answer\u0026#34;: null, \u0026#34;check_all\u0026#34;: false, \u0026#34;command\u0026#34;: \u0026#34;show ntp associations\u0026#34;, \u0026#34;prompt\u0026#34;: null, \u0026#34;sendonly\u0026#34;: false } }, \u0026#34;item\u0026#34;: \u0026#34;show ntp associations\u0026#34;, \u0026#34;stdout\u0026#34;: \u0026#34;address ref clock st when poll reach delay offset disp\\n*~162.159.200.123 10.72.8.95 3 64 128 377 16.354 -25.131 2.283\\n * sys.peer, # selected, + candidate, - outlyer, x falseticker, ~ configured\u0026#34;, \u0026#34;stdout_lines\u0026#34;: [ \u0026#34;address ref clock st when poll reach delay offset disp\u0026#34;, \u0026#34;*~162.159.200.123 10.72.8.95 3 64 128 377 16.354 -25.131 2.283\u0026#34;, \u0026#34; * sys.peer, # selected, + candidate, - outlyer, x falseticker, ~ configured\u0026#34; ] }, { \u0026#34;ansible_loop_var\u0026#34;: \u0026#34;item\u0026#34;, \u0026#34;changed\u0026#34;: false, \u0026#34;failed\u0026#34;: false, \u0026#34;invocation\u0026#34;: { \u0026#34;module_args\u0026#34;: { \u0026#34;answer\u0026#34;: null, \u0026#34;check_all\u0026#34;: false, \u0026#34;command\u0026#34;: \u0026#34;show clock\u0026#34;, \u0026#34;prompt\u0026#34;: null, \u0026#34;sendonly\u0026#34;: false } }, \u0026#34;item\u0026#34;: \u0026#34;show clock\u0026#34;, \u0026#34;stdout\u0026#34;: \u0026#34;02:19:22.598 UTC Mon Oct 21 2019\u0026#34;, \u0026#34;stdout_lines\u0026#34;: [ \u0026#34;02:19:22.598 UTC Mon Oct 21 2019\u0026#34; ] } ] } } PLAY RECAP *********************************************************************************************************************** r1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 In this execution we now have to get at the information within the results section. You do however also get the command in the output, as well as some other module arguments, which can be handy! To get at the results from show ntp associations you will need to use command_output.results[0].stdout and command_output.results[1].stdout to get at the results of show clock.\nSummary #I hope this has been valuable to you as a reader. With cli_command still relatively new, having been released in Ansible 2.7, I expect that it will continue to evolve. Take a look at the docs pages for these here:\ncli_command\nios_command\n","date":"2019-10-20","permalink":"https://josh-v.com/ansible-cli-vs-ios-command/","section":"Posts","summary":"\u003cp\u003eIn an earlier \u003ca href=\"https://josh-v.com/blog/2019/01/12/ansible-cli-vs-ios-high-level.html\" target=\"_blank\" rel=\"noreferrer\"\u003epost\u003c/a\u003e I\ncovered the differences between \u003ccode\u003eios_config\u003c/code\u003e and  \u003ccode\u003ecli_config\u003c/code\u003e. However I did not cover what the\ndifference was between \u003ccode\u003eios_command\u003c/code\u003e and \u003ccode\u003ecli_command\u003c/code\u003e. Most of the items covered there remain the\nsame. So this will be a post that mostly gets straight to it and sees what the difference is.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eA reminder that I am also putting playbooks used here out on Github. You can find this at:\n\u003ca href=\"https://github.com/jvanderaa/ansible-using_ios\" target=\"_blank\" rel=\"noreferrer\"\u003ehttps://github.com/jvanderaa/ansible-using_ios\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"differences\" class=\"relative group\"\u003eDifferences \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#differences\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eFirst, for the \u003ccode\u003ecli_commands\u003c/code\u003e module, you must be using a connection method of \u003ccode\u003enetwork_cli\u003c/code\u003e. You\nshould not use \u003ccode\u003econnection: local\u003c/code\u003e for this module. Note that the \u003ccode\u003ecli_command\u003c/code\u003e can also be used\nwith multiple device types, including multiple vendors. Take a look at the \u003ccode\u003ecli_command\u003c/code\u003e\ndocumentation page that there is a link at the bottom of the post.\u003c/p\u003e","title":"Ansible differences between ios command and cli command"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/cli_command/","section":"Tags","summary":"","title":"Cli_command"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/ios_command/","section":"Tags","summary":"","title":"Ios_command"},{"content":"As I restarted looking at how I\u0026rsquo;m continuing my education on the Network Automation and certification realm I asked the question \u0026ldquo;How are you simulating your network environment?\u0026rdquo; At the same time there has been thought on the idea of leveraging cloud resources to gain experience there.\nFirst requirement for me is that whatever tool/simulation set that I use it has to work. That being said, I need to be able to generate configurations, connect devices to each other, and have packets flow through the simulated network, just like any other network.\nSecond requirement is that I desire the solution to be economical. As a budget for this there wasn\u0026rsquo;t a lot of money left to be throwing around.\nAsking around, the third softer requirement is the solution should have a GUI of some sorts to make things work quickly so you aren\u0026rsquo;t fussing around with creating your own middleware solution.\nMy answer then to this at this point in time (2019-08-04) is EVE-NG. There is a strong possibility of this changing in the near future based on what I saw at 2019 Cisco Live to Cisco\u0026rsquo;s VIRL, but at the moment, EVE-NG and GNS3 both meet the requirements.\nIf you are looking for the part about how I get at devices in the EVE-NG network jump down to \u0026ldquo;EVE-NG for Automation Practice\u0026rdquo;.\nThis is not going to be a post on getting started on using the solutions. This post assumes that you are up and running with EVE locally on your network already. There are links further down that do help though for getting connectivity.\nRequirements for automation # Must be able to simulate larger networks Must be able to SSH to the devices directly for automation (Not just click on and get a console window) Evaluation (In my mind, no formal written down) #First when looking at the cloud side of things for running EVE-NG, I had built out an instance of EVE-NG in Google Cloud with the help of @showipintbri\u0026rsquo;s article on EVE-NG in the Cloud. As I looked at what I had already done with a bare metal host it appeared that I would need to create a VPN to be able to get at the network behind the cloud of an EVE-NG. Looking at the pricing on a VPN tunnel per minute/hour with Google Cloud, I made the evaluation that doing this in the cloud would not be economical.\nGNS3 has been a solid main stay for some time in the Network simulation world. There is nothing wrong with it. I have been successful in reaching into the GNS3 simulated world from a real network. There are some instructions on the web about how to do so. I had basically followed this instruction set (that has been removed, using the way back machine to get the old post) - Connect GNS3 to the Internet.\nFor me to get started quickly, I had recently installed an EVE-NG bare metal installation. That is the route that I have chosen at the moment to get started quickly. For instructions on doing a bare metal installation of EVE-NG I followed the online docs located on the EVE-NG main page - EVE-NG Bare Metal Install.\nEVE-NG for Automation Practice and Testing #So now how do we get access to the network? First within EVE-NG I Add a New Network to the project. I make sure that it is set to:\nNumber of Networks to build: 1 Name/Prefix: Internet (Be creative if you wish) Type: bridge The type of bridge is what we are looking for to enable the connectivity.\nConnecting your router #First thing you need to do within EVE-NG is to add a router and connect it to your Outside network. I\u0026rsquo;ve done so as shown here:\nThen the configuration on this Cisco edge device I have configured the following on the interface that has been connected to the outside.\n1 2 3 4 5 6 interface GigabitEthernet0/0 ip address dhcp duplex full speed 1000 media-type rj45 end This allows the device to come online and get a network address. If you wanted to prescribe what address it is in a static fashion, that is something you can do too. I like to use DHCP to verify that the device is in fact connected to the home network.\nRouting #For connectivity this is where I like to start with OSPF to peer with my home firewall. Why? First, because the firewall at home supports OSPF and why not use routing! Secondly it is more practice with OSPF. Much of the time in my career has been spent at places where EIGRP is the predominant IGP. It always helps to continue to gain experience. This is where you could use a static route for your device to send the routes back into your EVE-NG environment. You\u0026rsquo;ll want to make sure that you have routes for the networks that are in the network and that they do not overlap with your existing home network.\nTest Connections #Once connected, I make sure that I make sure that I\u0026rsquo;m able to connect successfully with SSH before starting the Ansible work. You could create and get started with a playbook and test, but I found it is easier to verify that you have SSH connectivity natively to your devices just like you would with a non-virtualized network.\nAutomation Testing #Now that there is connectivity to the devices on the box that you can SSH to each device from your home network, you are able to do testing of various playbooks. Start with some simple show commands using the ios_command or the newer cli_command module. And then debug it. Head on over to the post my earlier post Ansible - working with command output for some samples on getting started with Cisco devices.\nHope this is something that is helpful for you at home already running EVE locally!\n","date":"2019-08-04","permalink":"https://josh-v.com/eveng-for-autoamtion-practice-and-testing/","section":"Posts","summary":"\u003cp\u003eAs I restarted looking at how I\u0026rsquo;m continuing my education on the Network\nAutomation and certification realm I asked the question \u0026ldquo;How are you simulating\nyour network environment?\u0026rdquo; At the same time there has been thought on the idea\nof leveraging cloud resources to gain experience there.\u003c/p\u003e\n\u003cp\u003eFirst requirement for me is that whatever tool/simulation set that I use it has\nto work. That being said, I need to be able to generate configurations, connect\ndevices to each other, and have packets flow through the simulated network, just\nlike any other network.\u003c/p\u003e","title":"EVE-NG for Automation Practice and Testing"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/eveng/","section":"Tags","summary":"","title":"Eveng"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/network-simulation/","section":"Tags","summary":"","title":"Network Simulation"},{"content":"In this post I\u0026rsquo;m going to be taking a deeper dive into the new in Ansible 2.8 IOS BGP module. This may be one of the more complex modules to date and I\u0026rsquo;ll try to make it as simple as possible.\nFor a reminder about the BGP protocol is that this is the predominate protocol that runs the Internet. It is used to peer up with other companies and is what helps to make the Internet great. This is a very powerful protocol, and has been expanded to support many things. This is also a protocol that is heavily used in modern data centers.\nOn this module there are a TON of parameters (OK - 47 parameters). That is going to be too many to list out. If you want to take a look at each and every one of the parameters (which I do recommend doing at times, or at least going to the examples) check out the link above that takes you to the Ansible documentation.\nLet\u0026rsquo;s dive on in.\nNote: In this I will be working with a \u0026ldquo;fixed version\u0026rdquo; of the ios_bgp module. In working on the post it was found that next-hop-self was not getting applied when used in the module. This is fixed in a coming release of Ansible. As of 2.8.1 this is still broken. See https://github.com/ansible/ansible/pull/58789 for more information.\nObservations #A couple of general observations and my take on the module before getting into the lab and demo portions. This module is a great start on simplifying what can be a very complex configuration with BGP. By its nature BGP has a deep and complex configuration because of how flexible and how much has been stuffed into the BGP protocol. It\u0026rsquo;s being used within Data Centers of single tenants! This is not going to be a post about BGP however - you can find plenty of those elsewhere that are more in depth at this point.\nThis module gets the basics spot on. I\u0026rsquo;m going to look to leverage this wherever I can. That said however, there are still a few pieces that I haven\u0026rsquo;t been able to figure out how to do with this, and first comes the ISP world. Where there are multiple VRFs configured within BGP. I\u0026rsquo;m hopeful that this can be expanded in the future to support VRF configuration as well.\nAll that said, this is a complex module, and has a lot of great standardization to it. Take a look at the module definition in the Ansible docs.\nA very impressive part to this as well is that the redistribution from multiple protocols is covered within the module. Route maps can be applied on redistributions as well as the network advertisements. It\u0026rsquo;s going to continue to improve!\nLab Setup #Lab Devices #For this module in particular I went ahead and designed a new lab so we can dig deep into the setup of various methods of BGP. First we are having R2 as the edge of the lab, heading out Gig0/1 towards the Internet. R2 is acting as a single router within an ISP in this instance. R1 is the edge of the virtualized environment which allows me to leverage Ansible from my machine as the control machine. R3 and R4 will be on the edge of the enterprise network, with R5 originating some routes via EIGRP to the routers on R3 and R4.\nNetworks #Routes being advertised by R5 are two /25 networks out of the 203.0.113.0/24 network. All of the addressing in the \u0026ldquo;production\u0026rdquo; area of this enterprise are using RFC5737 address space. These are:\n198.51.100.0/24 203.0.113.0/24 192.0.2.0/24 Scenario #For demonstration purposes the configuration will be getting done on only R3 from a text perspective. There will however be the modules on the Github page, and in a follow on subsequent video demonstration of the playbook.\n1 2 3 4 5 6 - name: \u0026#34;PLAY 1: Get Configuration Backup to verify connectivity\u0026#34; - name: \u0026#34;TASK 1: Verify Config Backup\u0026#34; - name: \u0026#34;PLAY 2: Setup R2\u0026#34; - name: \u0026#34;TASK 1: Setup eBGP Peers\u0026#34; - name: \u0026#34;PLAY 3: Setup R4\u0026#34; - name: \u0026#34;TASK 1: Setup BGP Peers\u0026#34; This is going to walk through getting R3 to eBGP peer with R2 as a 3rd party connection, and to R4 as an iBGP peer for internal BGP. This will not work with the internal routing protocols. We assume that these are already all set to go.\nInitial Routing Configuration #This is more just to show where we are and that there is nothing configured for BGP on R3.\n1 2 R3#show run | sec bgp R3# First BGP Neighbor #The first BGP neighbor we should bring up is within the same AS. Let\u0026rsquo;s make sure that we are able to get BGP going to that within your same autonomous system and control before bringing up an exterior peer. In this example we will be building a BGP peer to 198.51.100.2 with the AS65500.\nPlaybook Start - Add iBGP peer #Getting started with building the internal BGP connection the task does get a touch lengthy, so leveraging copy and paste and finding the fields with the help of the module documentation this is the play to build that iBGP neighbor:\n- name: \u0026#34;PLAY 1: Setup iBGP Peer to R4\u0026#34; connection: network_cli hosts: r3 become: yes become_method: enable tasks: - name: \u0026#34;TASK 1: Setup iBGP Peer\u0026#34; ios_bgp: config: bgp_as: 65500 router_id: 10.0.0.3 log_neighbor_changes: true neighbors: - neighbor: 198.51.100.2 remote_as: 65500 activate: true timers: keepalive: 15 holdtime: 45 min_neighbor_holdtime: 5 description: R4 networks: - prefix: 198.51.100.0 masklen: 24 - prefix: 203.0.113.0 masklen: 24 address_family: - afi: ipv4 safi: unicast neighbors: - neighbor: 198.51.100.2 activate: yes next_hop_self: yes operation: merge register: ibgp_peer1 - name: \u0026#34;TASK 2: Debug output\u0026#34; debug: msg: \u0026#34;{{ ibgp_peer1 }}\u0026#34; Lines 10, 11, 12 are very common on the BGP configuration. Let\u0026rsquo;s walk through some of these.\nLine 10: Sets the locally running BGP AS on the router\nLine 11: Sets the router-id to be used for the BGP process\nLine 12: Sets logging of neighbor changes to true\nLines 13-21: Sets configuration of neighbor detail, outside of the address family\nLines 22-26: Are used for what networks we want to advertise from BGP\nLines 27-33: Used for BGP address family configuration updates\nLines 34-36: Identify our neighbor within the address family\nLine 37: The operation style from Merge, Replace, Override or Delete\nPlaybook iBGP - Execution # 1 2 3 - name: \u0026#34;PLAY 1: Setup iBGP Peer to R4\u0026#34; - name: \u0026#34;TASK 1: Setup iBGP Peer\u0026#34; - name: \u0026#34;TASK 2: Debug output\u0026#34; There are 2 tasks so we can see the output. The first task is to setup an iBGP peer. We get to see the output on the second task.\nTask 2 output has all of the router configurations that are going to be applied. Before the change there are no neighbors established. On the console there is an immediate neighbor established on the iBGP side of things with this configuration.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 PLAY [PLAY 1: Setup iBGP Peer to R4] ******************************************* TASK [TASK 1: Setup iBGP Peer] ************************************************* changed: [r3] TASK [TASK 2: Debug output] **************************************************** ok: [r3] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;router bgp 65500\u0026#34;, \u0026#34;bgp router-id 10.0.0.3\u0026#34;, \u0026#34;bgp log-neighbor-changes\u0026#34;, \u0026#34;neighbor 198.51.100.2 remote-as 65500\u0026#34;, \u0026#34;neighbor 198.51.100.2 timers 15 45 5\u0026#34;, \u0026#34;neighbor 198.51.100.2 description R4\u0026#34;, \u0026#34;network 198.51.100.0 mask 255.255.255.0\u0026#34;, \u0026#34;network 203.0.113.0 mask 255.255.255.128\u0026#34;, \u0026#34;address-family ipv4\u0026#34;, \u0026#34;no auto-summary\u0026#34;, \u0026#34;neighbor 198.51.100.2 activate\u0026#34;, \u0026#34;neighbor 198.51.100.2 next-hop-self\u0026#34;, \u0026#34;exit-address-family\u0026#34;, \u0026#34;exit\u0026#34; ], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* r3 : ok=2 changed=1 unreachable=0 failed=0 s kipped=0 rescued=0 ignored=0 Pretty straight to the point, that we have a complete BGP configuration getting deployed. A second run of the playbook should be idempotent, however, when executing the show run to get the configuration of the device and the network is subnetted on its proper class boundary the Ansible playbook will re-execute the command.\nIn working on this I have opened up a bug report on the module to see if this can be made idempotent. See Github Ansible Issue #59083\nSecond neighbor - the ISP connection #Let\u0026rsquo;s get to adding the second BGP connection. We will add a few more pieces of information onto the single task of creating a full BGP configuration.\nFirst let\u0026rsquo;s take a look at the BGP table on the router at this time, there is only one neighbor:\n1 2 3 4 5 6 7 8 9 10 11 12 13 BGP router identifier 10.0.0.3, local AS number 65500 BGP table version is 37404, main routing table version 37404 11 network entries using 1584 bytes of memory 12 path entries using 960 bytes of memory 6/6 BGP path/bestpath attribute entries using 912 bytes of memory 1 BGP AS-PATH entries using 24 bytes of memory 0 BGP route-map cache entries using 0 bytes of memory 0 BGP filter-list cache entries using 0 bytes of memory BGP using 3480 total bytes of memory BGP activity 18/6 prefixes, 18708/18696 paths, scan interval 60 secs Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd 198.51.100.2 4 65500 37400 63 37404 0 0 00:13:39 11 THe playbook is now:\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: \u0026#34;PLAY 1: Setup iBGP Peer to R4\u0026#34; connection: network_cli hosts: r3 become: yes become_method: enable tasks: - name: \u0026#34;TASK 1: Setup iBGP Peer\u0026#34; ios_bgp: config: bgp_as: 65500 router_id: 10.0.0.3 log_neighbor_changes: true neighbors: - neighbor: 198.51.100.2 remote_as: 65500 timers: keepalive: 15 holdtime: 45 min_neighbor_holdtime: 5 description: R4 - neighbor: 192.0.2.1 remote_as: 65510 timers: keepalive: 15 holdtime: 45 min_neighbor_holdtime: 5 description: ISP Neighbor 1 networks: - prefix: 198.51.100.0 masklen: 24 - prefix: 203.0.113.0 masklen: 25 - prefix: 203.0.113.128 masklen: 25 address_family: - afi: ipv4 safi: unicast auto_summary: no neighbors: - neighbor: 198.51.100.2 activate: yes next_hop_self: yes - neighbor: 192.0.2.1 activate: yes operation: merge register: bgp_setup - name: \u0026#34;SUMMARY TASK: Debug output\u0026#34; debug: msg: - \u0026#34;{{ bgp_setup }}\u0026#34; ... Adding Second Neighbor - Execution #The execution of the playbook is straight forward again. As expected a second neighbor statement is created with the remote-as, timers, and description. The module also activates the neighbor.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 PLAY [PLAY 1: Setup iBGP Peer to R4] ******************************************* TASK [TASK 1: Setup iBGP Peer] ************************************************* changed: [r3] TASK [SUMMARY TASK: Debug output] ********************************************** ok: [r3] =\u0026gt; { \u0026#34;msg\u0026#34;: [ { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;router bgp 65500\u0026#34;, \u0026#34;neighbor 192.0.2.1 remote-as 65510\u0026#34;, \u0026#34;neighbor 192.0.2.1 timers 15 45 5\u0026#34;, \u0026#34;neighbor 192.0.2.1 description ISP Neighbor 1\u0026#34;, \u0026#34;network 198.51.100.0 mask 255.255.255.0\u0026#34;, \u0026#34;network 203.0.113.128 mask 255.255.255.128\u0026#34;, \u0026#34;address-family ipv4\u0026#34;, \u0026#34;no auto-summary\u0026#34;, \u0026#34;neighbor 192.0.2.1 activate\u0026#34;, \u0026#34;exit-address-family\u0026#34;, \u0026#34;exit\u0026#34; ], \u0026#34;failed\u0026#34;: false } ] } PLAY RECAP ********************************************************************* r3 : ok=2 changed=1 unreachable=0 failed=0 s kipped=0 rescued=0 ignored=0 Taking a look at the BGP table, we now have 2 neighbors formed instead of just the one.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 BGP router identifier 10.0.0.3, local AS number 65500 BGP table version is 58006, main routing table version 58006 11 network entries using 1584 bytes of memory 19 path entries using 1520 bytes of memory 9/6 BGP path/bestpath attribute entries using 1368 bytes of memory 1 BGP AS-PATH entries using 24 bytes of memory 0 BGP route-map cache entries using 0 bytes of memory 0 BGP filter-list cache entries using 0 bytes of memory BGP using 4496 total bytes of memory BGP activity 21/9 prefixes, 29013/28994 paths, scan interval 60 secs Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd 192.0.2.1 4 65510 31 30 56929 0 0 00:03:26 6 198.51.100.2 4 65500 57996 99 58006 0 0 00:21:06 11 Summary #The module library keeps expanding. Originally I was taken back on the number of different modules being created that had a specialty to it. Take a look at the number of modules available for Ansible 2.9 and NXOS! I now see the benefit, and this module is a great addition the IOS module family. There are still areas that aren\u0026rsquo;t covered that may be better suited to be done with a Jinja template, but this is a great start on the BGP world for IOS.\nHope that this has helped someone along the way!\n","date":"2019-07-13","permalink":"https://josh-v.com/ansible-ios-bgp-module/","section":"Posts","summary":"\u003cp\u003eIn this post I\u0026rsquo;m going to be taking a deeper dive into the new in Ansible 2.8\n\u003ca href=\"https://docs.ansible.com/ansible/2.8/modules/ios_bgp_module.html\" target=\"_blank\" rel=\"noreferrer\"\u003eIOS BGP\u003c/a\u003e\nmodule. This may be one of the more complex modules to date and I\u0026rsquo;ll try to\nmake it as simple as possible.\u003c/p\u003e\n\u003cp\u003eFor a reminder about the BGP protocol is that this is the predominate protocol\nthat runs the Internet. It is used to peer up with other companies and is what\nhelps to make the Internet great. This is a very powerful protocol, and has been\nexpanded to support many things. This is also a protocol that is heavily used in\nmodern data centers.\u003c/p\u003e","title":"Ansible IOS BGP Module"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/ios_bgp/","section":"Tags","summary":"","title":"Ios_bgp"},{"content":"Today we are taking a look at the newest module out for Cisco ASA Ansible module - asa_og. This one is particularly exciting for the configurations that are being managed heavily with Object Groups on firewalls. I\u0026rsquo;m particularly excited to review the asa_og module, time to dig in.\nNew in this post is the finished playbooks being added to Github. I\u0026rsquo;m hoping that this may be helpful and I am uploading the contents to Github for more to be able to see and get access to if necessary. This will improve as I continue.\nhttps://github.com/jvanderaa/ansible-asa_work\nNote\nWhen working with this module there is not an option to save the configuration available with it. Please remember this in your playbook logic. If needing to save the configuration there are options. Take a look for samples on my previous post Saving Configurations which does not include an example with ASAs yet. I will have to write a follow up post on this at which point I will update hte link and content.\nParameters #First we are going to take a look at the particular parameters to get started, and what our options are for them. The items in bold are the ones that are required by the module.\ndescription: Description for the object group, good for documenting the purpose group_object: This is a list for items within the group group_type: network-object, service-object, or port-object host_ip: List of host addresses within the object group ip_mask: List of IPs and masks for use in object groups name: Name of the object group port_eq: Single port for port-object port_range: Range for a port-object protocol: UDP/TCP/TCP-UDP service_cfg: Service object configuration protocol, direction, range or port state: present/absent/replace to manage the state of the object For the straight forward parameters this seems like it is something that will be very handy to use.\nStarting Lab Setup #For this module we are starting with an effectively blank configuration on the firewalls. In future posts I will come back to this and show complete firewall policy management using Ansible.\nSo the lab looks very much the same at the moment as some of the other posts, which is below:\nSample goals:\nCreate an object group for internal addresses (RFC1918) Create an object group for external DNS services (Google DNS, Cloudflare, Quad9) Create a service group for DNS and NTP services Verify than a service group for just NTP_ONLY does not exist Leveraging Ansible OG for creating the modules #Creating RFC1918 Group #Let\u0026rsquo;s tackle the first item. First to show the configuration on the ASA firewall for the object group:\n1 2 fw01# show object-group fw01# show run | i RFC 1918 We see that there is not the object group that is desired to be there.\nWe are going to modify the Playbook created in the last blog post for asa_command and change it to managing our object groups. Starting out it will look like this:\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: ASA OG Working connection: network_cli hosts: asa_firewalls gather_facts: no become: yes become_method: enable tasks: - name: \u0026#34;TASK 1: Set RFC1918 Object Group\u0026#34; asa_og: name: RFC1918_Networks group_type: network-object state: present description: RFC1918 Local Networks ip_mask: - 10.0.0.0 255.0.0.0 - 172.16.0.0 255.240.0.0 - 192.168.0.0 255.255.0.0 register: output - name: \u0026#34;TASK 2: Print output of show interfaces\u0026#34; debug: msg: \u0026#34;{{ output }}\u0026#34; Now to run the playbook, the expect that the object group will be on the firewall.\nPlaybook Execution #The output from the playbook execution gives us exactly what we were looking for.\nTask 1: Connects to the ASA and runs the commands, there is a change as the ASA did not have the object group previously Task 2: Output from the previous task shows the commands that were run and the fact that the device was changed. First Run # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 PLAY [ASA OG Working] ********************************************************** TASK [TASK 1: Set RFC1918 Object Group] **************************************** changed: [asa1] TASK [TASK 2: Print output of RFC1918 Object Group] **************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;object-group network RFC1918_Networks\u0026#34;, \u0026#34;description RFC1918 Local Networks\u0026#34;, \u0026#34;network-object 10.0.0.0 255.0.0.0\u0026#34;, \u0026#34;network-object 172.16.0.0 255.240.0.0\u0026#34;, \u0026#34;network-object 192.168.0.0 255.255.0.0\u0026#34; ], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* asa1 : ok=2 changed=1 unreachable=0 failed=0 s kipped=0 rescued=0 ignored=0 Re-running the playbook again, we see that the module is idempotent. Being that we can safely run this continuously and not have any changes unless they are necessary.\nSecond Run # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 PLAY [ASA OG Working] ********************************************************** TASK [TASK 1: Set RFC1918 Object Group] **************************************** ok: [asa1] TASK [TASK 2: Print output of RFC1918 Object Group] **************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;commands\u0026#34;: [], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* asa1 : ok=2 changed=0 unreachable=0 failed=0 s kipped=0 rescued=0 ignored=0 Adding onto the previous playbook to add the second group #Continuing within this playbook we will create the second object group that will be used, the external DNS providers will be added as host objects to a new group for EXTERNAL_DNS_NTP.\nLet\u0026rsquo;s get straight to the play update. We will create a new task for this second operation and then output the debug summary.\nPlaybook Setup - Adding Host IP group #We have added the second task to the playbook here, with another debug so we can see when the changes are being made.\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: ASA OG Working connection: network_cli hosts: asa_firewalls gather_facts: no become: yes become_method: enable tasks: - name: \u0026#34;TASK 1: Set RFC1918 Object Group\u0026#34; asa_og: name: RFC1918_Networks group_type: network-object state: present description: RFC1918 Local Networks ip_mask: - 10.0.0.0 255.0.0.0 - 172.16.0.0 255.240.0.0 - 192.168.0.0 255.255.0.0 register: output - name: \u0026#34;TASK 2: Set External DNS/NTP Providers Object Group\u0026#34; asa_og: name: EXTERNAL_DNS_NTP group_type: network-object state: present description: External DNS Providers (CloudFlare, Google, Quad9, Umbrella) host_ip: - 1.1.1.1 - 8.8.8.8 - 9.9.9.9 - 208.67.222.222 - 208.67.220.220 register: output2 - name: \u0026#34;DEBUG 1: Print output of RFC1918 Object Group\u0026#34; debug: msg: \u0026#34;{{ output }}\u0026#34; - name: \u0026#34;DEBUG 2: Print output of External DNS Group\u0026#34; debug: msg: \u0026#34;{{ output2 }}\u0026#34; Playbook Execution # Task 1: Comes back OK, the object group is as defined and does not need to get updated Task 2: Adds the second object group that we were anticipating adding to the firewall Debug 1: Shows that there was no change by the changed output being set to false Debug 2: Once again shows the changed flag is set to True and the commands executed on the device 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 PLAY [ASA OG Working] ********************************************************** TASK [TASK 1: Set RFC1918 Object Group] **************************************** ok: [asa1] TASK [TASK 2: Set External DNS/NTP Providers Object Group] ********************* changed: [asa1] TASK [DEBUG 1: Print output of RFC1918 Object Group] *************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;commands\u0026#34;: [], \u0026#34;failed\u0026#34;: false } } TASK [DEBUG 2: Print output of External DNS Group] ***************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;object-group network EXTERNAL_DNS_NTP\u0026#34;, \u0026#34;network-object host 1.1.1.1\u0026#34;, \u0026#34;network-object host 8.8.8.8\u0026#34;, \u0026#34;network-object host 9.9.9.9\u0026#34;, \u0026#34;network-object host 208.67.222.222\u0026#34;, \u0026#34;network-object host 208.67.220.220\u0026#34;, \u0026#34;description External DNS Providers (CloudFlare, Google, Quad9, Umbr ella)\u0026#34; ], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ************************************************************************** asa1 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Playbook Results on ASA #As expected, we get the new items added to the configuration. When we look at the before and after on the configuration of the ASA we now see that we have the second object group, exactly as we expected.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 fw01# show object-group object-group network RFC1918_Networks description: RFC1918 Local Networks network-object 10.0.0.0 255.0.0.0 network-object 172.16.0.0 255.240.0.0 network-object 192.168.0.0 255.255.0.0 fw01# fw01# fw01# fw01# show object-group object-group network RFC1918_Networks description: RFC1918 Local Networks network-object 10.0.0.0 255.0.0.0 network-object 172.16.0.0 255.240.0.0 network-object 192.168.0.0 255.255.0.0 object-group network EXTERNAL_DNS_NTP description: External DNS Providers (CloudFlare, Google, Quad9, Umbrella) network-object host 1.1.1.1 network-object host 8.8.8.8 network-object host 9.9.9.9 network-object host 208.67.222.222 network-object host 208.67.220.220 Adding on the Port Group #Now we need to complete the setup by adding a port group to the playbook so when a policy is built that the hosts can communicate on the specific ports. To start off the policy will use only UDP ports 53 (DNS) and 123 (NTP).\nYes DNS is also on TCP/53, but for this we will stick to only the UDP side for non large requests.\nIssue: When working on this I came across an issue with the asa_og module and my particular setup (Python3.7.2) with respects to Ansible 2.8. The concatenation engine would error out combining strings (the actual commands) and integers. The work around on this that you see in the playbook is that the ports are surrounded by quotes. This makes them strings instead of integers and the module works. I have opened an issue on github for this. https://github.com/ansible/ansible/issues/58258 if you wish to check on the status.\nPlaybook - Adding in port-object group creation #As expected there is a third task now that will be for creating the port object. From the module parameters we are now using parameters of protocol and port_eq. These are expected parameters for creating a port group.\nPlaybook Task Design - Port Group #Here is the task that is added with the group-type set to port-object:\n- name: \u0026#34;TASK 3: Add Port Group\u0026#34; asa_og: name: SVC_OBJ_DNS_NTP group_type: port-object state: present description: DNS and NTP ports protocol: udp port_eq: - 53 - 123 register: output3 This brings the full playbook to looking like this:\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: ASA OG Working connection: network_cli hosts: asa_firewalls gather_facts: no become: yes become_method: enable tasks: - name: \u0026#34;TASK 1: Set RFC1918 Object Group\u0026#34; asa_og: name: RFC1918_Networks group_type: network-object state: present description: RFC1918 Local Networks ip_mask: - 10.0.0.0 255.0.0.0 - 172.16.0.0 255.240.0.0 - 192.168.0.0 255.255.0.0 register: output - name: \u0026#34;TASK 2: Set External DNS/NTP Providers Object Group\u0026#34; asa_og: name: EXTERNAL_DNS_NTP group_type: network-object state: present description: External DNS Providers (CloudFlare, Google, Quad9, Umbrella) host_ip: - 1.1.1.1 - 8.8.8.8 - 9.9.9.9 - 208.67.222.222 - 208.67.220.220 register: output2 - name: \u0026#34;TASK 3: Add Port Group\u0026#34; asa_og: name: SVC_OBJ_DNS_NTP group_type: port-object state: present description: DNS and NTP ports protocol: udp port_eq: - 53 - 123 register: output3 - name: \u0026#34;DEBUG 1: Print output of RFC1918 Object Group\u0026#34; debug: msg: \u0026#34;{{ output }}\u0026#34; - name: \u0026#34;DEBUG 2: Print output of External DNS Group\u0026#34; debug: msg: \u0026#34;{{ output2 }}\u0026#34; - name: \u0026#34;DEBUG 3: Print output of adding Port Group\u0026#34; debug: msg: \u0026#34;{{ output3 }}\u0026#34; ... Playbook Execution #Task 1: Reports OK, as there are no changes here - as expected Task 2: Also reports OK, as there should be no changes - as expected Task 3: Creates the port-object to be used Debug 1: Shows no changes Debug 2: Shows no changes Debug 3: Shows that the changed flag is set to True and that the changes sent to the device creates the port object\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 PLAY [ASA OG Working] ********************************************************** TASK [TASK 1: Set RFC1918 Object Group] **************************************** ok: [asa1] TASK [TASK 2: Set External DNS/NTP Providers Object Group] ********************* ok: [asa1] TASK [TASK 3: Add Port Group] ************************************************** changed: [asa1] TASK [DEBUG 1: Print output of RFC1918 Object Group] *************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;commands\u0026#34;: [], \u0026#34;failed\u0026#34;: false } } TASK [DEBUG 2: Print output of External DNS Group] ***************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;commands\u0026#34;: [], \u0026#34;failed\u0026#34;: false } } TASK [DEBUG 3: Print output of adding Port Group] ****************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;object-group service SVC_OBJ_DNS_NTP udp\u0026#34;, \u0026#34;port-object eq 53\u0026#34;, \u0026#34;port-object eq 123\u0026#34;, \u0026#34;description DNS and NTP ports\u0026#34; ], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* asa1 : ok=6 changed=1 unreachable=0 failed=0 s kipped=0 rescued=0 ignored=0 Changes on the ASA #And as we are use to seeing, we see the update on the ASA itself:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 fw01# show object-group object-group network RFC1918_Networks description: RFC1918 Local Networks network-object 10.0.0.0 255.0.0.0 network-object 172.16.0.0 255.240.0.0 network-object 192.168.0.0 255.255.0.0 object-group network EXTERNAL_DNS_NTP description: External DNS Providers (CloudFlare, Google, Quad9, Umbrella) network-object host 1.1.1.1 network-object host 8.8.8.8 network-object host 9.9.9.9 network-object host 208.67.222.222 network-object host 208.67.220.220 fw01# fw01# fw01# show object-group object-group network RFC1918_Networks description: RFC1918 Local Networks network-object 10.0.0.0 255.0.0.0 network-object 172.16.0.0 255.240.0.0 network-object 192.168.0.0 255.255.0.0 object-group network EXTERNAL_DNS_NTP description: External DNS Providers (CloudFlare, Google, Quad9, Umbrella) network-object host 1.1.1.1 network-object host 8.8.8.8 network-object host 9.9.9.9 network-object host 208.67.222.222 network-object host 208.67.220.220 object-group service SVC_OBJ_DNS_NTP udp description: DNS and NTP ports port-object eq domain port-object eq ntp Removing groups #The last thing to demo is the state: absent of the module. What I have seen in testing at the moment is that the module does not delete the group all together, but removes the object members. Let\u0026rsquo;s take a look at this in action.\nSetup - Deletion #First I went ahead and created a new group (using Ansible of course). This is the old group for DNS that is no longer being used. So we should clean that up of course. This module only deletes items from within the object-group, it will NOT remove an entire object-group.\nThe firewall configuration has the following for object groups:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 object-group network RFC1918_Networks description: RFC1918 Local Networks network-object 10.0.0.0 255.0.0.0 network-object 172.16.0.0 255.240.0.0 network-object 192.168.0.0 255.255.0.0 object-group network EXTERNAL_DNS_NTP description: External DNS Providers (CloudFlare, Google, Quad9, Umbrella) network-object host 1.1.1.1 network-object host 8.8.8.8 network-object host 9.9.9.9 network-object host 208.67.222.222 network-object host 208.67.220.220 object-group service SVC_OBJ_DNS_NTP udp description: DNS and NTP ports port-object eq domain port-object eq ntp object-group service DNS_ONLY udp description: DNS ports port-object eq domain Task Created - Absent state #Here is the task with the state changed from present to absent:\n- name: \u0026#34;TASK 4: Remove Extra Group\u0026#34; asa_og: name: DNS_ONLY group_type: port-object state: absent protocol: udp port_eq: - domain register: output4 Playbook Execution - Absent state for an object group #Task 1 - 3: These are the idempotent adds. There is not any changes being made, so these remain OK. Task 4: Removes the particular item from within a group. So you will need to call out all of the objects that you want to have missing from here. The next task will take a look at another helpful state of replace.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 PLAY [ASA OG Working] ********************************************************** TASK [TASK 1: Set RFC1918 Object Group] **************************************** ok: [asa1] TASK [TASK 2: Set External DNS/NTP Providers Object Group] ********************* ok: [asa1] TASK [TASK 3: Add Port Group] ************************************************** ok: [asa1] TASK [TASK 4: Remove Extra Group] ********************************************** changed: [asa1] TASK [DEBUG 1: Print output of RFC1918 Object Group] *************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;commands\u0026#34;: [], \u0026#34;failed\u0026#34;: false } } TASK [DEBUG 2: Print output of External DNS Group] ***************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;commands\u0026#34;: [], \u0026#34;failed\u0026#34;: false } } TASK [DEBUG 3: Print output of adding Port Group] ****************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;commands\u0026#34;: [], \u0026#34;failed\u0026#34;: false } } TASK [DEBUG 4: Print output of removing extra Port Group] ********************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;object-group service DNS_ONLY udp\u0026#34;, \u0026#34;no port-object eq domain\u0026#34; ], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* asa1 : ok=8 changed=1 unreachable=0 failed=0 s kipped=0 rescued=0 ignored=0 Firewall After #Here we see that the port object that we asked to remove is gone. If there were other object items in the object-group they would still remain.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 fw01# show object-group object-group network RFC1918_Networks description: RFC1918 Local Networks network-object 10.0.0.0 255.0.0.0 network-object 172.16.0.0 255.240.0.0 network-object 192.168.0.0 255.255.0.0 object-group network EXTERNAL_DNS_NTP description: External DNS Providers (CloudFlare, Google, Quad9, Umbrella) network-object host 1.1.1.1 network-object host 8.8.8.8 network-object host 9.9.9.9 network-object host 208.67.222.222 network-object host 208.67.220.220 object-group service SVC_OBJ_DNS_NTP udp description: DNS and NTP ports port-object eq domain port-object eq ntp object-group service DNS_ONLY udp description: DNS ports port-object eq domain fw01# fw01# ! AFTER THE CHANGE fw01# show object-group object-group network RFC1918_Networks description: RFC1918 Local Networks network-object 10.0.0.0 255.0.0.0 network-object 172.16.0.0 255.240.0.0 network-object 192.168.0.0 255.255.0.0 object-group network EXTERNAL_DNS_NTP description: External DNS Providers (CloudFlare, Google, Quad9, Umbrella) network-object host 1.1.1.1 network-object host 8.8.8.8 network-object host 9.9.9.9 network-object host 208.67.222.222 network-object host 208.67.220.220 object-group service SVC_OBJ_DNS_NTP udp description: DNS and NTP ports port-object eq domain port-object eq ntp object-group service DNS_ONLY udp description: DNS ports State: Replace #Originally this was not on the radar to include in this post, but I have found it very helpful. What replace will do for you is allow you to set this as the \u0026ldquo;standard\u0026rdquo;. So if there are extraneous items in the object group replace will remove anything extra. If there are any items missing from the object group it will add them in.\nTasks - Replace #I\u0026rsquo;m going to modify Task 3 from earlier to add some extra ports to the port-group and change domain to dns even though domain is the proper ASA shorthand for port 53. The two tasks are now this:\n- name: \u0026#34;TASK 3: Add Port Group\u0026#34; asa_og: name: SVC_OBJ_DNS_NTP group_type: port-object state: present description: DNS and NTP ports protocol: udp port_eq: - \u0026#34;ntp\u0026#34; - \u0026#34;dns\u0026#34; - \u0026#34;5353\u0026#34; - \u0026#34;553\u0026#34; - \u0026#34;353\u0026#34; register: output3 - name: \u0026#34;TASK 5: Fix the DNS Port Group\u0026#34; asa_og: name: SVC_OBJ_DNS_NTP group_type: port-object state: replace description: DNS and NTP ports protocol: udp port_eq: - \u0026#34;domain\u0026#34; - \u0026#34;ntp\u0026#34; register: output5 Replace Task Execution #Let\u0026rsquo;s get right to looking at the execution based on the summary above.\nFirewall Before #Here we see that the object group SVC_OBJ_DNS_NTP has a lot more entries than one should expect.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 fw01# show object-group object-group network RFC1918_Networks description: RFC1918 Local Networks network-object 10.0.0.0 255.0.0.0 network-object 172.16.0.0 255.240.0.0 network-object 192.168.0.0 255.255.0.0 object-group network EXTERNAL_DNS_NTP description: External DNS Providers (CloudFlare, Google, Quad9, Umbrella) network-object host 1.1.1.1 network-object host 8.8.8.8 network-object host 9.9.9.9 network-object host 208.67.222.222 network-object host 208.67.220.220 object-group service SVC_OBJ_DNS_NTP udp description: DNS and NTP ports port-object eq domain port-object eq ntp port-object eq dnsix port-object eq 5353 port-object eq 553 port-object eq 353 Replace - Full Playbook #The full playbook with all of the debugs and the tasks.\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: ASA OG Working connection: network_cli hosts: asa_firewalls gather_facts: no become: yes become_method: enable tasks: - name: \u0026#34;TASK 1: Set RFC1918 Object Group\u0026#34; asa_og: name: RFC1918_Networks group_type: network-object state: present description: RFC1918 Local Networks ip_mask: - 10.0.0.0 255.0.0.0 - 172.16.0.0 255.240.0.0 - 192.168.0.0 255.255.0.0 register: output - name: \u0026#34;TASK 2: Set External DNS/NTP Providers Object Group\u0026#34; asa_og: name: EXTERNAL_DNS_NTP group_type: network-object state: present description: External DNS Providers (CloudFlare, Google, Quad9, Umbrella) host_ip: - 1.1.1.1 - 8.8.8.8 - 9.9.9.9 - 208.67.222.222 - 208.67.220.220 register: output2 - name: \u0026#34;TASK 3: Add Port Group\u0026#34; asa_og: name: SVC_OBJ_DNS_NTP group_type: port-object state: present description: DNS and NTP ports protocol: udp port_eq: - \u0026#34;ntp\u0026#34; - \u0026#34;dnsix\u0026#34; - \u0026#34;5353\u0026#34; - \u0026#34;553\u0026#34; - \u0026#34;353\u0026#34; register: output3 - name: \u0026#34;TASK 4: Remove Extra Group\u0026#34; asa_og: name: DNS_ONLY group_type: port-object state: absent protocol: udp port_eq: - domain register: output4 - name: \u0026#34;TASK 5: Fix the DNS Port Group\u0026#34; asa_og: name: SVC_OBJ_DNS_NTP group_type: port-object state: replace description: DNS and NTP ports protocol: udp port_eq: - \u0026#34;domain\u0026#34; - \u0026#34;ntp\u0026#34; register: output5 - name: \u0026#34;DEBUG 1: Print output of RFC1918 Object Group\u0026#34; debug: msg: \u0026#34;{{ output }}\u0026#34; - name: \u0026#34;DEBUG 2: Print output of External DNS Group\u0026#34; debug: msg: \u0026#34;{{ output2 }}\u0026#34; - name: \u0026#34;DEBUG 3: Print output of adding Port Group\u0026#34; debug: msg: \u0026#34;{{ output3 }}\u0026#34; - name: \u0026#34;DEBUG 4: Print output of removing extra Port Group\u0026#34; debug: msg: \u0026#34;{{ output4 }}\u0026#34; - name: \u0026#34;DEBUG 5: Print output of Port Group Replace\u0026#34; debug: msg: \u0026#34;{{ output5 }}\u0026#34; ... Execution #Task 1-4: All check out OK, there are no changes being made\nTask 5: This is that additional task to fix the item properly\nDebug 1-4: All show no changes\nDebug 5: Shows that the changes were made, including add domain and removing a bunch of extra items\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 PLAY [ASA OG Working] ********************************************************** TASK [TASK 1: Set RFC1918 Object Group] **************************************** ok: [asa1] TASK [TASK 2: Set External DNS/NTP Providers Object Group] ********************* ok: [asa1] TASK [TASK 3: Add Port Group] ************************************************** ok: [asa1] TASK [TASK 4: Remove Extra Group] ********************************************** ok: [asa1] TASK [TASK 5: Fix the DNS Port Group] ****************************************** changed: [asa1] TASK [DEBUG 1: Print output of RFC1918 Object Group] *************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;commands\u0026#34;: [], \u0026#34;failed\u0026#34;: false } } TASK [DEBUG 2: Print output of External DNS Group] ***************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;commands\u0026#34;: [], \u0026#34;failed\u0026#34;: false } } TASK [DEBUG 3: Print output of adding Port Group] ****************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;commands\u0026#34;: [], \u0026#34;failed\u0026#34;: false } } TASK [DEBUG 4: Print output of removing extra Port Group] ********************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;commands\u0026#34;: [], \u0026#34;failed\u0026#34;: false } } TASK [DEBUG 5: Print output of Port Group Replace] ***************************** ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;object-group service SVC_OBJ_DNS_NTP udp\u0026#34;, \u0026#34;port-object eq domain\u0026#34;, \u0026#34;no port-object eq 553\u0026#34;, \u0026#34;no port-object eq 353\u0026#34;, \u0026#34;no port-object eq 5353\u0026#34;, \u0026#34;no port-object eq dnsix\u0026#34; ], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* asa1 : ok=10 changed=1 unreachable=0 failed=0 s kipped=0 rescued=0 ignored=0 Summary #This module is a terrific module if you are asked to manage ASA policy. This is very complete and should be part of your toolset for managing ASA devices. I do foresee a significant amount of use out of the state: replace setup in getting object groups to a declared state.\nAgain, very important as well, do not forget to save your configurations at the end if making changes.\nI hope that this has been informative!\n","date":"2019-06-23","permalink":"https://josh-v.com/ansible-asa-og/","section":"Posts","summary":"\u003cp\u003eToday we are taking a look at the newest module out for Cisco ASA Ansible\nmodule - \u003ca href=\"https://docs.ansible.com/ansible/latest/modules/asa_og_module.html\" target=\"_blank\" rel=\"noreferrer\"\u003easa_og\u003c/a\u003e.\nThis one is particularly exciting for the configurations that are being managed\nheavily with Object Groups on firewalls. I\u0026rsquo;m particularly excited to review the\n\u003cstrong\u003easa_og\u003c/strong\u003e module, time to dig in.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eNew\u003c/strong\u003e in this post is the finished playbooks being added to Github. I\u0026rsquo;m hoping\nthat this may be helpful and I am uploading the contents to Github for more to\nbe able to see and get access to if necessary. This will improve as I continue.\u003c/p\u003e","title":"Ansible ASA OG Module"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/asa/","section":"Tags","summary":"","title":"Asa"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/asa_og/","section":"Tags","summary":"","title":"Asa_og"},{"content":"Today will be a touch shorter post, but it is good to be back at it. In this post I will be taking a quick look around at the asa_command module, as we start down the path with looking at the ASA modules in Ansible. This is spurned on a little bit by Ansible 2.8 coming out with an Object Group specific module. I will be looking into that further in a future post.\nFor the set of posts regarding the ASA, we will be starting with a pretty bare configuration on the device. We will have just a management IP address and the ability to SSH to the device.\nModule Documentation #Module documentation page can be found here.\nLab Configuration #The device has bare basic configuration on it. Here we see that it has just a management IP address on it.\nfw01# show int ip brie Interface IP-Address OK? Method Status Protocol GigabitEthernet0/0 unassigned YES unset administratively down up GigabitEthernet0/1 unassigned YES unset administratively down up GigabitEthernet0/2 unassigned YES unset administratively down up Management0/0 172.16.0.254 YES CONFIG up up Using the playbook #Parameters #There are a couple of key parameters on this module for getting started are:\ncommands: A list of commands to send to the device; this can be one, or several commands within a list context: used for firewalls in multi-context mode, which context do you want to run the command(s) in Simple first Playbook #This is a simple playbook that will issue two commands. We will access both of them in different tasks within the play. Taking a look at the play we are executing the task with two commands, a show int ip brie and a ping to Google DNS.\nPlaybook # --- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: ASA Command Output connection: network_cli hosts: asa_firewalls gather_facts: no become: yes become_method: enable tasks: - name: \u0026#34;TASK 1: Read output from ASA\u0026#34; asa_command: commands: - show int ip brief - ping 8.8.8.8 register: output - name: \u0026#34;TASK 2: Print output of show interfaces\u0026#34; debug: msg: \u0026#34;{{ output.stdout_lines.0 }}\u0026#34; - name: \u0026#34;TASK 3: Print output of pinging Google DNS\u0026#34; debug: msg: \u0026#34;{{ output.stdout_lines.1 }}\u0026#34; Tasks High Level #TASK 1 is when Ansible logs into the device and issues the two commands.\nTASK 2 we get the expected output of the show int ip brie and the commands TASK 3 we see that the device is able to successfully ping Google DNS\nThese are the tasks that are to be run via the playbook broken out:\n1 2 3 4 cat asa_command_demo.yml | grep TASK - name: \u0026#34;TASK 1: Read output from ASA\u0026#34; - name: \u0026#34;TASK 2: Print output of show interfaces\u0026#34; - name: \u0026#34;TASK 3: Print output of pinging Google DNS\u0026#34; Playbook Run #Execution of the playbook:\nTo see a video of this on Youtube - https://youtu.be/Wk-3Zg08oSw\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 PLAY [ASA Command Output] ********************************************************************* TASK [TASK 1: Read output from ASA] *********************************************************** ok: [asa1] TASK [TASK 2: Print output of show interfaces] ************************************************ ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: [ \u0026#34;Interface IP-Address OK? Method Status Protocol\u0026#34;, \u0026#34;GigabitEthernet0/0 unassigned YES unset administratively down up \u0026#34;, \u0026#34;GigabitEthernet0/1 unassigned YES unset administratively down up \u0026#34;, \u0026#34;GigabitEthernet0/2 unassigned YES unset administratively down up \u0026#34;, \u0026#34;Management0/0 172.16.0.254 YES CONFIG up up\u0026#34; ] } TASK [TASK 3: Print output of pinging Google DNS] ********************************************* ok: [asa1] =\u0026gt; { \u0026#34;msg\u0026#34;: [ \u0026#34;Type escape sequence to abort.\u0026#34;, \u0026#34;Sending 5, 100-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds:\u0026#34;, \u0026#34;!!!!!\u0026#34;, \u0026#34;Success rate is 100 percent (5/5), round-trip min/avg/max = 20/104/190 ms\u0026#34; ] } PLAY RECAP ************************************************************************************ asa1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Access Multiple Commands #This is another example of how to issue multiple commands against a device within a single task. For a deeper dive on that you can see an earlier post here.\nSummary #This is a solid starting out module for working with ASA firewalls. It does come in very handy with dealing and gathering information from the ASA firewall platform. I have used this for several things within a production environment, primarily for data gathering. Hopefully coming up I will be able to expand on this further in building out an ASA firewall.\nHope this was helpful!\n","date":"2019-06-20","permalink":"https://josh-v.com/ansible-asa-command/","section":"Posts","summary":"\u003cp\u003eToday will be a touch shorter post, but it is good to be back at it. In this\npost I will be taking a quick look around at the asa_command module, as we start\ndown the path with looking at the ASA modules in Ansible. This is spurned on a\nlittle bit by Ansible 2.8 coming out with an Object Group specific module. I\nwill be looking into that further in a future post.\u003c/p\u003e","title":"Ansible ASA Command Module"},{"content":"Today I\u0026rsquo;m going to take a look at a method to be able to save the configuration of a Cisco device to NVRAM (copy run start). I will be taking a look at multiple Cisco platforms to save changes done during an Ansible Playbook to NVRAM. There are options to save the configuration on every change within the modules such as ios_config or cli_config, however, this can slow down the execution of your playbook.\nFirst I will take a look at saving the configuration within its own task just for saving configuration. This is how I have many of my playbooks as I\u0026rsquo;m executing several tasks, breaking them out. I then have a dedicated task that will save the configuration. I do this for speed of the playbook execution. If there are multiple changes on tasks and each one of the tasks is saving the configuration, then there will be some significant time spent saving the configuration multiple times. In this I will take a look at the copy command execution, but also, the trick I like to use of the config module and just save_when parameter.\nThe second methodology I will take a look at is the saving the configuration within the task itself, using the save_when parameter. This is something that I use when I have simple playbooks, with only a couple of tasks that will modify the configuration.\nWe will be taking a look at how to do this with the following Cisco platforms:\nCisco IOS Cisco NXOS Cisco WLC Saving Configuration in one task #IOS / NXOS #The method I like to use to save the configuration is to use the ios_config module with the parameter of save_when set to always. No other parameter set, just the save_when. The nxos_config and ios_config modules both work in the same way. I\u0026rsquo;ll show both of the outputs, but no separate write up on the NXOS side.\nTo save the configuration then here is the task:\nPlaybook Definition #IOS\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: rtr02 gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; Save Configuration to NVRAM ios_config: save_when: always register: output - name: DEBUG \u0026gt;\u0026gt; output debug: msg: \u0026#34;{{ output }}\u0026#34; NXOS\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: nxos_switches gather_facts: no tasks: - name: NXOS \u0026gt;\u0026gt; Save Configuration to NVRAM nxos_config: save_when: always register: output - name: DEBUG \u0026gt;\u0026gt; output debug: msg: \u0026#34;{{ output }}\u0026#34; Playbook Execution #On the output from the playbook, you don\u0026rsquo;t get a lot of feedback that the config is copied other than the task being successful. However, since I\u0026rsquo;m using a vIOS image in my lab environment the console does show GRUB messages. The second output shows the successful saving of the configuration that was done as the Ansible Playbook was being executed.\nPlaybook Execution - IOS\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 PLAY [Switch config] *********************************************************** TASK [IOS \u0026gt;\u0026gt; Save Configuration to NVRAM] ************************************** changed: [rtr02] TASK [DEBUG \u0026gt;\u0026gt; output] ********************************************************* ok: [rtr02] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* rtr02 : ok=2 changed=1 unreachable=0 failed=0 GRUB Output - IOS\n1 2 *Mar 30 16:51:15.832: %GRUB-5-CONFIG_WRITING: GRUB configuration is being updated on disk. Please wait... *Mar 30 16:51:16.446: %GRUB-5-CONFIG_WRITTEN: GRUB configuration was written to disk successfully NXOS Execution\nHere the output is minimal, with the output reporting success as our only method to know that the configuration was in fact saved.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 PLAY [Switch config] *********************************************************** TASK [NXOS \u0026gt;\u0026gt; Save Configuration to NVRAM] ************************************* changed: [nxos01] TASK [DEBUG \u0026gt;\u0026gt; output] ********************************************************* ok: [nxos01] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* nxos01 : ok=2 changed=1 unreachable=0 failed=0 Method with IOS_Command #Not recommended, but if you must.\nHere there are two options for using a dedicated task for saving the configuration. The second method requires specific configuration to be added, which is something that I don\u0026rsquo;t really like to have to do. You would use the configuration file prompt quiet within the configuratoin of the IOS device, then you can use the ios_command module to issue copy run start.\nCisco WLC - Save Configuration aireos_command #I have not been able to test the methodology of using save_when on the Cisco Wireless Controllers yet, but the methodology that I have used is using the command module. The difficulty on the aireos_command module is that there really isn\u0026rsquo;t a methodology to handle prompts yet. This is actually very easy to overcome however on the module using escape characters.\nHere you see a \\r in the middle of the output before the response of y for do you wish to save. You do not need one on the end as there is an implicit carriage return at the end of any line within the modules.\nUsing the aireos_command module task looks like this:\n- name: WLC \u0026gt;\u0026gt; Save Configuration aireos_command: commands: - \u0026#34;save config\\ry\u0026#34; Saving Configurations on Each Task #When looking at the four modules of ios_config, nxos_config, cli_config, and aireos_config you will find that there is an parameter for either save or save_when. The parameter save is something that is being deprecated and I would not recommend using this. All of these modules will flag deprecation warnings on Ansible version 2.7.\nModule Deprecated Parameter Move Away Current Save Parameter ios_config save (yes, no) save_when (always, never, modified, changed) nxos_config save (yes, no) save_when (always, never, modified, changed) aireos_config save (yes, no) save_when (always, never, modified, changed) Note: I do not actively have a Cisco Wireless Controller available in my lab at the time of the writing. From working in my production environment, the configuration being save follows closely to that of the ios_config or nxos_config modules.\nModule Details Links #aireos_config\nios_config\nnxos_config\nParameter Choices # always never modified changed Choices Detail # When changes are made to the device running-configuration, the changes are not copied to non-volatile storage by default. Using this argument will change that before. If the argument is set to always, then the running-config will always be copied to the startup-config and the modified flag will always be set to True. If the argument is set to modified, then the running-config will only be copied to the startup-config if it has changed since the last save to startup-config. If the argument is set to never, the running-config will never be copied to the startup-config. If the argument is set to changed, then the running-config will only be copied to the startup-config if the task has made a change. changed was added in Ansible 2.6.\nios_config save_when Parameter #This is straight forward, when you want to save the configuration after a change within each task then you would set the parameter save_when to changed. This is advantages on simple playbooks. Next we will take a look at a playbook with the task set to changed.\nPlaybook Definition #At the start, I have modified the hostname of the router that I\u0026rsquo;m going to be working on. I have set the hostname to router02 which you can see with the prompt being router02# once entering enable mode. This playbook we will be changing the hostname of hte device to match that which is in the Ansible inventory file.\nHere is the playbook\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: rtr02 gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; Set hostname ios_config: lines: - hostname {{ inventory_hostname }} save_when: changed register: output - name: DEBUG \u0026gt;\u0026gt; output debug: msg: \u0026#34;{{ output }}\u0026#34; When looking at the startup configuration on the router this is what we have:\n1 2 router02#show start | i hostname hostname router02 Playbook Execution #We see that the configuration was updated with the command:\nhostname rtr02 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 PLAY [Switch config] *********************************************************** TASK [IOS \u0026gt;\u0026gt; Set hostname] ***************************************************** changed: [rtr02] TASK [DEBUG \u0026gt;\u0026gt; output] ********************************************************* ok: [rtr02] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;banners\u0026#34;: {}, \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;hostname rtr02\u0026#34; ], \u0026#34;failed\u0026#34;: false, \u0026#34;updates\u0026#34;: [ \u0026#34;hostname rtr02\u0026#34; ] } } PLAY RECAP ********************************************************************* rtr02 : ok=2 changed=1 unreachable=0 failed=0 After the execution we have the startup configuration with the new name, just as we expected\n1 2 rtr02#show start | i hostname hostname rtr02 Second Execution of the ios_config module #I\u0026rsquo;m going to run the same playbook once again, to show that the module has the smarts to not change the configuration since it is set. Note the changed output is set to false.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 PLAY [Switch config] *********************************************************** TASK [IOS \u0026gt;\u0026gt; Set hostname] ***************************************************** ok: [rtr02] TASK [DEBUG \u0026gt;\u0026gt; output] ********************************************************* ok: [rtr02] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* rtr02 : ok=2 changed=0 unreachable=0 failed=0 nxos_config save_when Parameter #Similar to the ios_config here is a run through of the same set of plays this time with a NXOS device.\nPlaybook Definition #Once again, I have the NXOS device hostname set to something different than we would like.\nThe startup configuration has the hostname of nxos_switch1 but the Ansible inventory has the name nxos01 for the inventory_name.\n1 2 nxos_switch1# show start | i hostname hostname nxos_switch1 THe playbook now looks like this:\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: nxos_switches gather_facts: no tasks: - name: NXOS \u0026gt;\u0026gt; Set hostname nxos_config: lines: - hostname {{ inventory_hostname }} save_when: changed register: output - name: DEBUG \u0026gt;\u0026gt; output debug: msg: \u0026#34;{{ output }}\u0026#34; Playbook Execution #Just as before we have the hostname change to match that of the Ansible inventory.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 PLAY [Switch config] *********************************************************** TASK [NXOS \u0026gt;\u0026gt; Set hostname] **************************************************** changed: [nxos01] TASK [DEBUG \u0026gt;\u0026gt; output] ********************************************************* ok: [nxos01] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;hostname nxos01\u0026#34; ], \u0026#34;failed\u0026#34;: false, \u0026#34;updates\u0026#34;: [ \u0026#34;hostname nxos01\u0026#34; ] } } PLAY RECAP ********************************************************************* nxos01 : ok=2 changed=1 unreachable=0 failed=0 The show start shows that the startup configuration was changed.\n1 2 nxos01# show start | i hostname hostname nxos_switch1 Summary #There are multiple methods available for saving the configuration to NVRAM in the Cisco world. The first method works very well when you have a large number of tasks and always wish to have the startup configuration match that of the running configuration from playbook execution. If there are small/simple playbooks with only one or two configurations being applied, it may be worth it to have the task save the configuration.\nHope this was helpful!\n","date":"2019-03-30","permalink":"https://josh-v.com/ansible-saving-cisco-configs-ios/","section":"Posts","summary":"\u003cp\u003eToday I\u0026rsquo;m going to take a look at a method to be able to save the configuration of a Cisco device to\nNVRAM (copy run start). I will be taking a look at multiple Cisco platforms to save changes done\nduring an Ansible Playbook to NVRAM. There are options to save the configuration on every change\nwithin the modules such as \u003cstrong\u003eios_config\u003c/strong\u003e or \u003cstrong\u003ecli_config\u003c/strong\u003e, however, this can slow down the\nexecution of your playbook.\u003c/p\u003e","title":"Ansible Saving Cisco Configs to NVRAM with Cisco Specific Modules"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/cisco_ios/","section":"Tags","summary":"","title":"Cisco_ios"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/cisco_nxos/","section":"Tags","summary":"","title":"Cisco_nxos"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/cisco_wlc/","section":"Tags","summary":"","title":"Cisco_wlc"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/saving_config/","section":"Tags","summary":"","title":"Saving_config"},{"content":"Update: ios_interface is to be deprecated as of Ansible 2.13\nIn this post I will be taking a deeper look at the ios_interface module. This module is used to configure individual interfaces on a Cisco IOS device. The documentation for the module is located here. In this module I did have to dig into the actual Python file, and that is located here.\nEdit: Had to update the link due to the change in Ansible coming in 2.10. I have hard linked to the IOS Interfaces module.\nThis module does not configure the layer 2 or layer 3 information on an interface. There are other modules that are used for configuring these particular pieces.\nA look at the Parameters #Required Parameters #This module only has a single required module.\nname: Name of the interface that is being configured, such as GigabitEthernet0/0/0 Using the module with just the one required item of name is pretty uneventful. If you wish to see the output I\u0026rsquo;m going to put that at the very bottom as an Appendix type item if you wish to see that output.\nOptional Parameters # aggregate: This is what you will need to use if you want to configure multiple interfaces within the same task execution (or a loop of course) delay: Time to wait before checking the state of an interface, defaults to 10 seconds description: Interface description, follows the Cisco command description under the interface configuration duplex: (full/half/auto) duplex settings in the interface configuration enabled: (yes/no) interface link status, should it be enabled/disabled mtu: MTU setting, follows the mtu configuration under the interface configuration neighbors: Checking for the operational state, using LLDP information, either with the sub-parameter host or port rx_rate: Stated as Receiver rate in bits per second, not sure what this does speed: Interface speed in Mbps, corresponds to the Cisco command speed under interface configuration tx_rate: Stated as Transmit rate in bits per second, not sure what it does Note - Operational State\nAs I\u0026rsquo;m writing (and not having tested yet) the operational state if you are configuring an interface and looking to validate the neighbors, you may want to up the delay time based on the LLDP neighbor timers. These timers may be longer than the default 10 seconds.\nrx_rate and tx_rate #From the documentation on the module this appears to perhaps to be related to the actual interface transmit and receive rates that is being reported by the device. The documentation has some references to ge and le which would be comparisons. Based on the Python file and the variables named want_tx_rate and want_rx_rate within the Python file, this does in fact appear to be related to the interface traffic amount.\nParameter Details: Aggregate #This is what I will say is a group of interfaces to configure within a single task. This is where you will configure multiples of the ios_interface task. To leverage this you will need to create a dictionary (Array) with the required parameters for the module.\nA look at the module in action #First, jumping deep in. Going to take a look at what it looks like to configure interfaces using the aggregate parameter. This is going to configure specific details about two interfaces on the switch itself.\nPre-Change Config #Before the change there is just the media-type and the negotiation set to auto. These are default out of the box.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 sw19#show run int gig0/1 Building configuration... Current configuration : 71 bytes ! interface GigabitEthernet0/1 media-type rj45 negotiation auto end sw19#show run int gig0/2 Building configuration... Current configuration : 71 bytes ! interface GigabitEthernet0/2 media-type rj45 negotiation auto end Playbook # --- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: sw19 gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; VLAN Updates ios_interface: aggregate: - {name: GigabitEthernet0/1, description: \u0026#34;First Ansible Configured Interface\u0026#34;, enabled: no} - {name: GigabitEthernet0/2, description: \u0026#34;Second Ansible Configured Interface\u0026#34;, enabled: yes} speed: 100 duplex: full register: output - name: DEBUG \u0026gt;\u0026gt; output debug: msg: \u0026#34;{{ output }}\u0026#34; Ansible Output #Here we see that the commands being sent to the device are to set the speed, duplex to full, interface description, and then shutting down the interface that was set to disabled.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 PLAY [Switch config] *********************************************************** TASK [IOS \u0026gt;\u0026gt; VLAN Updates] ***************************************************** changed: [sw19] TASK [DEBUG \u0026gt;\u0026gt; output] ********************************************************* ok: [sw19] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;interface GigabitEthernet0/1\u0026#34;, \u0026#34;speed 100\u0026#34;, \u0026#34;description First Ansible Configured Interface\u0026#34;, \u0026#34;duplex full\u0026#34;, \u0026#34;shutdown\u0026#34;, \u0026#34;interface GigabitEthernet0/2\u0026#34;, \u0026#34;speed 100\u0026#34;, \u0026#34;description Second Ansible Configured Interface\u0026#34;, \u0026#34;duplex full\u0026#34; ], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* sw19 : ok=2 changed=1 unreachable=0 failed=0 Post Execution Configuration #Working through this, it looks like the speed cannot be configured as autonegotation is set on the interface. I believe that this is something that is primarily set because of using a virtualized switch platform. I plan to open up a bug report on this soon. We see exactly what we expect in the configuration after the Ansible output. We see interface description configured on each interface, the interface shutdown or enabled.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 sw19#show run int gig0/1 Building configuration... Current configuration : 129 bytes ! interface GigabitEthernet0/1 description First Ansible Configured Interface shutdown media-type rj45 negotiation auto end sw19#show run int gig0/2 Building configuration... Current configuration : 120 bytes ! interface GigabitEthernet0/2 description Second Ansible Configured Interface media-type rj45 negotiation auto end Creating a Loopback Interface #In the second example of the playbook we will create additional Loopback addresses.\nPre-Change Configuration #Here is the output of the show ip int breif of the switch before adding loopbacks.\n1 2 3 4 5 6 7 8 9 10 11 12 13 Interface IP-Address OK? Method Status Protocol GigabitEthernet0/0 unassigned YES unset up up GigabitEthernet0/1 unassigned YES unset administratively down down GigabitEthernet0/2 unassigned YES unset up up GigabitEthernet0/3 unassigned YES unset up up GigabitEthernet1/0 unassigned YES unset up up GigabitEthernet1/1 unassigned YES unset up up GigabitEthernet1/2 unassigned YES unset up up GigabitEthernet1/3 unassigned YES unset up up Loopback0 10.100.100.100 YES manual up up Port-channel5 unassigned YES unset down down Port-channel6 unassigned YES unset down down Vlan2 172.16.1.2 YES manual up up Here we only see one loopback address, Loopback0.\nPlaybook #The playbook I\u0026rsquo;m going to add a loopback interface, but there will not be an address configured on it, you will need to use ios_l3_interface in conjunction with this if using ios_interface for loopbacks.\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: sw19 gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; VLAN Updates ios_interface: name: Loopback5 register: output - name: DEBUG \u0026gt;\u0026gt; output debug: msg: \u0026#34;{{ output }}\u0026#34; Ansible Output #I expect to see the configuration of just creating a loopback address. This is in fact what is seen upon executing the command.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 PLAY [Switch config] *********************************************************** TASK [IOS \u0026gt;\u0026gt; VLAN Updates] ***************************************************** changed: [sw19] TASK [DEBUG \u0026gt;\u0026gt; output] ********************************************************* ok: [sw19] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;interface Loopback5\u0026#34; ], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* sw19 : ok=2 changed=1 unreachable=0 failed=0 Switch Post Run #Now on the switch as expected we see another Loopback address added.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 sw19#show ip int brie Interface IP-Address OK? Method Status Protocol GigabitEthernet0/0 unassigned YES unset up up GigabitEthernet0/1 unassigned YES unset administratively down down GigabitEthernet0/2 unassigned YES unset up up GigabitEthernet0/3 unassigned YES unset up up GigabitEthernet1/0 unassigned YES unset up up GigabitEthernet1/1 unassigned YES unset up up GigabitEthernet1/2 unassigned YES unset up up GigabitEthernet1/3 unassigned YES unset up up Loopback0 10.100.100.100 YES manual up up Loopback5 unassigned YES unset up up Port-channel5 unassigned YES unset down down Port-channel6 unassigned YES unset down down Vlan2 172.16.1.2 YES manual up up Summary #Earlier in the week I started using this module in a production environment. I had been using just ios_config and moving down into the interface and issuing the shutdown or no shutdown of an interface. After coming across a couple of errors I decided to try the ios_interface module for the playbook. This worked out much better. Digging through this module further with this post I am finding that ios_interface is really good for interface state of up/down and the description of the interface. So you will want to use this in conjunction with the ios_l3_interface and ios_l2_interface to get the complete interface configuration with the modules.\nAppendix #Task with only the required Parameters (Loopback10) #First taking a look at the play with using a Loopback10 interface. There was previously no Loopback10 interface configured. The play looks like the following:\nPlay # --- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: sw19 gather_facts: no become: yes become_method: enable tasks: - name: IOS Interface \u0026gt;\u0026gt; Configure Loopback10 ios_interface: name: Loopback10 register: output - name: DEBUG \u0026gt;\u0026gt; output debug: msg: \u0026#34;{{ output }}\u0026#34; Play Output #On the output front, nothing surprising.\nThe commands sent to the device essentially are:\nconfig t interface Loopback10\nThis creates the interface that was not there previously and does not provide any other configuration.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 PLAY [Switch config] *********************************************************** TASK [IOS \u0026gt;\u0026gt; VLAN Updates] ***************************************************** changed: [sw19] TASK [DEBUG \u0026gt;\u0026gt; output] ********************************************************* ok: [sw19] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;interface Loopback10\u0026#34; ], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* sw19 : ok=2 changed=1 unreachable=0 failed=0 Task with only the required Parameters (GigabitEthernet0/1) #In this play the interface being configured will be moved from a Loopback interface to one of the physical interfaces on the device. There will once again be no parameters.\nPlay # --- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: sw19 gather_facts: no become: yes become_method: enable tasks: - name: IOS Interface \u0026gt;\u0026gt; Configure Gig0/1 ios_interface: name: GigabitEthernet0/1 register: output - name: DEBUG \u0026gt;\u0026gt; output debug: msg: \u0026#34;{{ output }}\u0026#34; Output #The Ansible module appears to check the running configuration as expected before stepping through. The output shows no commands being applied as the configuration on the interface already has the desired configuration (blank).\nPre-Configuration\n1 2 3 4 5 6 7 8 9 #show run int gig0/1 Building configuration... Current configuration : 71 bytes ! interface GigabitEthernet0/1 media-type rj45 negotiation auto end There are no pieces that need to be configured, so the output from the playbook execution is below. With no other parameters defined the module does nothing.\nPlay Execution\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 PLAY [Switch config] *********************************************************** TASK [IOS \u0026gt;\u0026gt; VLAN Updates] ***************************************************** ok: [sw19] TASK [DEBUG \u0026gt;\u0026gt; output] ********************************************************* ok: [sw19] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;commands\u0026#34;: [], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* sw19 : ok=2 changed=0 unreachable=0 failed=0 ","date":"2019-03-17","permalink":"https://josh-v.com/ansible-cisco-ios-interface/","section":"Posts","summary":"\u003cp\u003eUpdate: \u003ccode\u003eios_interface\u003c/code\u003e is to be deprecated as of Ansible 2.13\u003c/p\u003e\n\u003cp\u003eIn this post I will be taking a deeper look at the \u003cstrong\u003eios_interface\u003c/strong\u003e module. This module is used to\nconfigure individual interfaces on a Cisco IOS device. The documentation for the module is located\n\u003ca href=\"https://docs.ansible.com/ansible/latest/modules/ios_interface_module.html\" target=\"_blank\" rel=\"noreferrer\"\u003ehere\u003c/a\u003e.\nIn this module I did have to dig into the actual Python file, and that is located\n\u003ca href=\"https://github.com/ansible/ansible/blob/stable-2.9/lib/ansible/modules/network/ios/ios_interfaces.py\" target=\"_blank\" rel=\"noreferrer\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eEdit: Had to update the link due to the change in Ansible coming in 2.10. I have hard linked to\nthe IOS Interfaces module.\u003c/p\u003e","title":"Ansible Cisco IOS Interface Module"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/ios_interface/","section":"Tags","summary":"","title":"Ios_interface"},{"content":"Back to it finally. Going to take a look at the Ansible module ios_vlan. The purpose of this is to provide a declarative module for managing VLANs on IOS devices. In this I will be using IOSv-L2 images. There are a few interesting quirks (as I will call it) within the parameters for the module.\nModule Documentation #First, the module documentation page is here.\nGetting Started with the module #VLANs pre-module work #Starting out the switch is pretty bare as it relates to the number of VLANs. There is VLAN2 defined on the switch that has an uplink to the edge (of the lab) router. The base VLANs are the only other ones on the device:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #show vlan VLAN Name Status Ports ---- -------------------------------- --------- ------------------------------- 1 default active Gi0/1, Gi0/3, Gi1/0, Gi1/2 Gi1/3 2 TRANSIT active Gi0/0, Gi1/1 1002 fddi-default act/unsup 1003 token-ring-default act/unsup 1004 fddinet-default act/unsup 1005 trnet-default act/unsup VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2 ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------ 1 enet 100001 1500 - - - - - 0 0 2 enet 100002 1500 - - - - - 0 0 1002 fddi 101002 1500 - - - - - 0 0 1003 tr 101003 1500 - - - - - 0 0 1004 fdnet 101004 1500 - - - ieee - 0 0 1005 trnet 101005 1500 - - - ibm - 0 0 Remote SPAN VLANs ------------------------------------------------------------------------------ Primary Secondary Type Ports ------- --------- ----------------- ------------------------------------------ Building the Play #The module has the following parameters, required ones in bold. This skips over the deprecated parameters:\naggregate: List of VLANs definitions associated_interfaces: Checks for the operational state of the interface delay: default to 10 seconds, how long to wait for the declarative state to be seen interfaces: a list of interfaces that should have the VLAN assigned to it name: Name of the VLAN purge: Purge VLANs not defined in the aggregate parameter state: present/absent/active/suspend - the state that it should be in vlan_id: ID of the VLAN So what does aggregate mean? It sounds like that if you wanted to have a large list of VLANs, this is the way to go with a task. First attempt at seeing what it does, the following playbook was setup\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: sw19 gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; VLAN Updates ios_vlan: aggregate: - 2 - 5 vlan_id: 5 name: TEST VLAN 5 state: present register: command_output - name: DEBUG \u0026gt;\u0026gt; VLAN Update debug: msg: \u0026#34;{{ command_output }}\u0026#34; When executing it came across an error that gave some more insight that was not portrayed on the module definition page.\n1 2 3 4 5 6 7 8 9 10 ansible-playbook output_test.yml -i ./lab_hosts PLAY [Switch config] *********************************************************** TASK [IOS \u0026gt;\u0026gt; VLAN Updates] ***************************************************** fatal: [sw19]: FAILED! =\u0026gt; {\u0026#34;changed\u0026#34;: false, \u0026#34;msg\u0026#34;: \u0026#34;parameters are mutually exclusive: vlan_id, aggregate\u0026#34;} to retry, use: --limit @/Users/joshv/Documents/Ansible/output_test.retry PLAY RECAP ********************************************************************* sw19 : ok=0 changed=0 unreachable=0 failed=1 Modifying the playbook with the fatal error message out. It now looks like this:\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: sw19 gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; VLAN Updates ios_vlan: aggregate: - { \u0026#39;vlan_id\u0026#39;: 2, \u0026#39;name\u0026#39;: \u0026#39;TRANSIT\u0026#39; } - { \u0026#39;vlan_id\u0026#39;: 5, \u0026#39;name\u0026#39;: \u0026#39;Test VLAN\u0026#39; } state: present register: command_output - name: DEBUG \u0026gt;\u0026gt; VLAN Update debug: msg: \u0026#34;{{ command_output }}\u0026#34; This will now deploy in aggregate all of the VLANs that are being defined in the list of dictionaries. Looking at the output this is what is now on the switch:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #show vlan VLAN Name Status Ports ---- -------------------------------- --------- ------------------------------- 1 default active Gi0/1, Gi0/3, Gi1/0, Gi1/2 Gi1/3 2 TRANSIT active Gi0/0, Gi1/1 5 Test VLAN active Gi0/2 1002 fddi-default act/unsup 1003 token-ring-default act/unsup 1004 fddinet-default act/unsup 1005 trnet-default act/unsup VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2 ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------ 1 enet 100001 1500 - - - - - 0 0 2 enet 100002 1500 - - - - - 0 0 5 enet 100005 1500 - - - - - 0 0 1002 fddi 101002 1500 - - - - - 0 0 1003 tr 101003 1500 - - - - - 0 0 1004 fdnet 101004 1500 - - - ieee - 0 0 1005 trnet 101005 1500 - - - ibm - 0 0 Remote SPAN VLANs ------------------------------------------------------------------------------ Primary Secondary Type Ports ------- --------- ----------------- ------------------------------------------ Changing the VLANs on the device #Removing a VLAN that is not supposed to be on the device is incredibly simple with this aggregate feature as well. If we change the play to looking like this\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: sw19 gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; VLAN Updates ios_vlan: aggregate: - { \u0026#39;vlan_id\u0026#39;: 2, \u0026#39;name\u0026#39;: \u0026#39;TRANSIT\u0026#39;, state: present } - { \u0026#39;vlan_id\u0026#39;: 5, \u0026#39;name\u0026#39;: \u0026#39;Test VLAN\u0026#39;, state: absent } register: command_output - name: DEBUG \u0026gt;\u0026gt; VLAN Update debug: msg: \u0026#34;{{ command_output }}\u0026#34; The resulting play execution shows that the VLAN is removed from the command output.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 PLAY [Switch config] *********************************************************** TASK [IOS \u0026gt;\u0026gt; VLAN Updates] ***************************************************** changed: [sw19] TASK [DEBUG \u0026gt;\u0026gt; VLAN Update] **************************************************** ok: [sw19] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;no vlan 5\u0026#34; ], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* sw19 : ok=2 changed=1 unreachable=0 failed=0 Adding a VLAN and assigning to Interface #Want to create an interface and assign it to a VLAN quickly? Here is where the ios_vlan module may be able to help very quickly. In the lab it is very simple as there are only a few interfaces on the layer 2 switch that we have.\nPlay Definition # --- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: sw19 gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; VLAN Updates ios_vlan: vlan_id: 12 name: test-vlan interfaces: - GigabitEthernet1/2 register: command_output - name: DEBUG \u0026gt;\u0026gt; VLAN Update debug: msg: \u0026#34;{{ command_output }}\u0026#34; Playbook Execution - Adding a VLAN #This play execution will both add a VLAN to the switch, and assign the interfaces to the VLAN as an access port. With the output from the execution the module registers each of the commands that are being issued to the switch. This shows the VLAN is first created, then goes into the interface assigned as a parameter. Lastly it sets that interface to being an access interface in the VLAN.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 PLAY [Switch config] *********************************************************** TASK [IOS \u0026gt;\u0026gt; VLAN Updates] ***************************************************** changed: [sw19] TASK [DEBUG \u0026gt;\u0026gt; VLAN Update] **************************************************** ok: [sw19] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;vlan 12\u0026#34;, \u0026#34;name test-vlan\u0026#34;, \u0026#34;interface GigabitEthernet1/2\u0026#34;, \u0026#34;switchport mode access\u0026#34;, \u0026#34;switchport access vlan 12\u0026#34; ], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* sw19 : ok=2 changed=1 unreachable=0 failed=0 IOS_VLAN - Purge Parameter #Originally when looking at this module I kind of passed over purge parameter. It was mentioned that this is used with conjunction of the aggregrate parameter. My original thinking when I read aggregate was that this was somehow related to Link Aggregation Control Protocol. Now with looking at the module much more in depth, I see that was a wrong assumption (in case it was for others). This adds significant power, to make sure that a switch is configured the way that you define within a play/task and stays configured that way. If some rogue actor has added a VLAN, how will you ever know. So for this next test, I went and created three manual VLANs on the switch for VLANs 10, 13, and 100.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 VLAN Name Status Ports ---- -------------------------------- --------- ------------------------------- 1 default active Gi0/1, Gi0/3, Gi1/0, Gi1/3 2 TRANSIT active Gi0/0, Gi1/1 5 Test VLAN active Gi0/2 10 MANUAL active 12 QB VLAN active Gi1/2 13 MANUAL13 active 100 MANUAL100 active 1002 fddi-default act/unsup 1003 token-ring-default act/unsup 1004 fddinet-default act/unsup 1005 trnet-default act/unsup VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2 ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------ 1 enet 100001 1500 - - - - - 0 0 2 enet 100002 1500 - - - - - 0 0 5 enet 100005 1500 - - - - - 0 0 10 enet 100010 1500 - - - - - 0 0 12 enet 100012 1500 - - - - - 0 0 13 enet 100013 1500 - - - - - 0 0 VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2 ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------ 100 enet 100100 1500 - - - - - 0 0 1002 fddi 101002 1500 - - - - - 0 0 1003 tr 101003 1500 - - - - - 0 0 1004 fdnet 101004 1500 - - - ieee - 0 0 1005 trnet 101005 1500 - - - ibm - 0 0 Remote SPAN VLANs ------------------------------------------------------------------------------ Primary Secondary Type Ports ------- --------- ----------------- ------------------------------------------ Play Setup - Purge VLANs # --- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: sw19 gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; VLAN Updates ios_vlan: aggregate: - { \u0026#39;vlan_id\u0026#39;: 2, \u0026#39;name\u0026#39;: \u0026#39;TRANSIT\u0026#39;, state: present } - { \u0026#39;vlan_id\u0026#39;: 5, \u0026#39;name\u0026#39;: \u0026#39;Test VLAN\u0026#39;, state: present } - { \u0026#39;vlan_id\u0026#39;: 12, \u0026#39;name\u0026#39;: \u0026#39;THE TEST VLAN\u0026#39;, state: present } purge: yes register: command_output - name: DEBUG \u0026gt;\u0026gt; VLAN Update debug: msg: \u0026#34;{{ command_output }}\u0026#34; PLAY EXECUTION #Below you will find the play execution and the resulting commands sent to the switch. To show this I did have to run the playbook twice as the bug that I found did not run properly the first time.\nOne important note that I did find when testing this playbook, at least within Ansible version 2.7.5 to use the purge function, you must use the keyword yes instead of true. If you use true the purge function will not work.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 PLAY [Switch config] *********************************************************** TASK [IOS \u0026gt;\u0026gt; VLAN Updates] ***************************************************** changed: [sw19] TASK [DEBUG \u0026gt;\u0026gt; VLAN Update] **************************************************** ok: [sw19] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;vlan 5\u0026#34;, \u0026#34;name Test VLAN\u0026#34;, \u0026#34;vlan 12\u0026#34;, \u0026#34;name THE TEST VLAN\u0026#34;, \u0026#34;no vlan 10\u0026#34;, \u0026#34;no vlan 13\u0026#34;, \u0026#34;no vlan 100\u0026#34;, \u0026#34;no vlan 1002\u0026#34;, \u0026#34;no vlan 1003\u0026#34;, \u0026#34;no vlan 1004\u0026#34;, \u0026#34;no vlan 1005\u0026#34; ], \u0026#34;failed\u0026#34;: false } } PLAY RECAP ********************************************************************* sw19 : ok=2 changed=1 unreachable=0 failed=0 Resulting VLAN Configuration # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #show vlan VLAN Name Status Ports ---- -------------------------------- --------- ------------------------------- 1 default active Gi0/1, Gi0/3, Gi1/0, Gi1/3 2 TRANSIT active Gi0/0, Gi1/1 5 Test VLAN active Gi0/2 12 THE TEST VLAN active Gi1/2 1002 fddi-default act/unsup 1003 token-ring-default act/unsup 1004 fddinet-default act/unsup 1005 trnet-default act/unsup VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2 ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------ 1 enet 100001 1500 - - - - - 0 0 2 enet 100002 1500 - - - - - 0 0 5 enet 100005 1500 - - - - - 0 0 12 enet 100012 1500 - - - - - 0 0 1002 fddi 101002 1500 - - - - - 0 0 1003 tr 101003 1500 - - - - - 0 0 1004 fdnet 101004 1500 - - - ieee - 0 0 1005 trnet 101005 1500 - - - ibm - 0 0 Remote SPAN VLANs ------------------------------------------------------------------------------ Primary Secondary Type Ports ------- --------- ----------------- ------------------------------------------ Summary #In summary there are some pieces that need to get worked out. This was the first time that I had taken a look at the module. For the work that I\u0026rsquo;ve done previously I was just using Jinja2 templates to assign VLAN configuration to an interface. Looking closer at this module there is a lot of power to make sure that the proper VLANs are configured everywhere that you need, and be able to eliminate others. This module is definitely something that you should keep in your pocket.\n","date":"2019-03-09","permalink":"https://josh-v.com/ansible-ios-vlan/","section":"Posts","summary":"\u003cp\u003eBack to it finally. Going to take a look at the Ansible module \u003cstrong\u003eios_vlan\u003c/strong\u003e. The purpose of this is\nto provide a declarative module for managing VLANs on IOS devices. In this I will be using IOSv-L2\nimages. There are a few interesting quirks (as I will call it) within the parameters for the module.\u003c/p\u003e\n\u003ch2 id=\"module-documentation\" class=\"relative group\"\u003eModule Documentation \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#module-documentation\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eFirst, the module documentation page is\n\u003ca href=\"https://docs.ansible.com/ansible/latest/modules/ios_vlan_module.html\" target=\"_blank\" rel=\"noreferrer\"\u003ehere\u003c/a\u003e.\u003c/p\u003e","title":"Ansible IOS VLAN"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/ios_vlan/","section":"Tags","summary":"","title":"Ios_vlan"},{"content":"Today\u0026rsquo;s post is going to be a short and sweet one (unless I get to writing two). I\u0026rsquo;m going to take a look at ios_banner module. This one is pretty much straight to the point, what it states, modifying the banner on an IOS device. There are multiple reasons to want to manipulate the banner on a Cisco device. We will leave those reasons to you and the organization that you are a part of for that. For now, we will take a real quick look at the module.\nModule Documentation #First, the module documentation page is here.\nGetting Started with the Lab #I\u0026rsquo;m starting out with no banner on the page of my system as evident from this login:\nCisco Router Login\n1 2 3 Escape character is \u0026#39;^]\u0026#39;. Username: IOS Banner Play / tasks #Let\u0026rsquo;s go ahead and apply a banner to the login with the following tasks:\n- name: IOS \u0026gt;\u0026gt; Set banner to single login ios_banner: banner: login state: present text: \u0026#34;Quick banner, this device is being managed by Ansible.\u0026#34; register: output - name: DEBUG \u0026gt;\u0026gt; Output debug: msg: \u0026#34;{{ output }}\u0026#34; I now have the following banner showing up on the login to the Cisco device over SSH.\nBanner on Router\n1 Quick banner, this device is being managed by Ansible. Output of ios_banner #Let\u0026rsquo;s take a look at the output of the ios_banner module when saved to a variable. We get the following output to the screen:\n1 2 3 4 5 6 7 8 9 10 TASK [DEBUG \u0026gt;\u0026gt; Output] ********************************************************* ok: [rtr01] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;banner login @\\nQuick banner, this device is being managed by Ansible.\\n@\u0026#34; ], \u0026#34;failed\u0026#34;: false } } There are three \u0026ldquo;outputs\u0026rdquo; to the variable. Changed, commands, and failed.\nChanged looks to be the true/false of was the device changed as part of the play execution.\ncomamnds are what actually was run on the Cisco device from config mode.\nfailed is the state of the task, true/false\nMultiline banner #To set a multi-line banner on something, it is as simple as using the | or \u0026gt; keys that are part of YAML. These again are functions known within YAML and not something specific to Ansible, so this is something that would carry over between languages/tools that are using YAML as the formatting.\ntasks: - name: IOS \u0026gt;\u0026gt; Set banner to single login ios_banner: banner: login state: present text: | ===This device is being managed by Ansible=== Making changes at your own risk! register: output - name: DEBUG \u0026gt;\u0026gt; Output debug: msg: \u0026#34;{{ output }}\u0026#34; This has successfully added a multiple line banner to the configuration:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 TASK [IOS \u0026gt;\u0026gt; Set banner to single login] *************************************** changed: [rtr01] TASK [DEBUG \u0026gt;\u0026gt; Output] ********************************************************* ok: [rtr01] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;banner login @\\n===This device is being managed by Ansible===\\nMaki ng changes at your own risk!\\n@\u0026#34; ], \u0026#34;failed\u0026#34;: false } } A quick look at the configuration itself in IOS:\n1 2 3 4 5 6 ! banner login ^C ===This device is being managed by Ansible=== Making changes at your own risk! ^C ! This automatically puts the ^C as the character delineation for you, as that was not something that was specified within the module itself.\nSetting Multiple Banners #If you want to set multiple banners, say exec, login, and motd, you will want to change this to leveraging with_items. This way Ansible will iterate and set all of these. Here is the Play.\nNotice the changes on line 3 below has been changed from the banner login to the variable {{ item }}. with_items has been added on line 8. And we have set this to change the banner for motd (Message of the Day), login, and exec.\n- name: IOS \u0026gt;\u0026gt; Set banner to single login ios_banner: banner: \u0026#34;{{ item }}\u0026#34; state: present text: | ===This device is being managed by Ansible=== Making changes at your own risk! with_items: - motd - login - exec register: output - name: DEBUG \u0026gt;\u0026gt; Output debug: msg: \u0026#34;{{ output }}\u0026#34; Output 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 TASK [IOS \u0026gt;\u0026gt; Set banner to single login] *************************************** changed: [rtr01] =\u0026gt; (item=motd) ok: [rtr01] =\u0026gt; (item=login) changed: [rtr01] =\u0026gt; (item=exec) TASK [DEBUG \u0026gt;\u0026gt; Output] ********************************************************* ok: [rtr01] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;msg\u0026#34;: \u0026#34;All items completed\u0026#34;, \u0026#34;results\u0026#34;: [ { \u0026#34;_ansible_ignore_errors\u0026#34;: null, \u0026#34;_ansible_item_label\u0026#34;: \u0026#34;motd\u0026#34;, \u0026#34;_ansible_item_result\u0026#34;: true, \u0026#34;_ansible_no_log\u0026#34;: false, \u0026#34;_ansible_parsed\u0026#34;: true, \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;banner motd @\\n===This device is being managed by Ansible===\\nMaking changes at your own risk!\\n@\u0026#34; ], \u0026#34;failed\u0026#34;: false, \u0026#34;invocation\u0026#34;: { \u0026#34;module_args\u0026#34;: { \u0026#34;auth_pass\u0026#34;: null, \u0026#34;authorize\u0026#34;: null, \u0026#34;banner\u0026#34;: \u0026#34;motd\u0026#34;, \u0026#34;host\u0026#34;: null, \u0026#34;password\u0026#34;: null, \u0026#34;port\u0026#34;: null, \u0026#34;provider\u0026#34;: null, \u0026#34;ssh_keyfile\u0026#34;: null, \u0026#34;state\u0026#34;: \u0026#34;present\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;===This device is being managed by Ansible===\\nMaking changes at your own risk!\\n\u0026#34;, \u0026#34;timeout\u0026#34;: null, \u0026#34;username\u0026#34;: null } }, \u0026#34;item\u0026#34;: \u0026#34;motd\u0026#34; }, { \u0026#34;_ansible_ignore_errors\u0026#34;: null, \u0026#34;_ansible_item_label\u0026#34;: \u0026#34;login\u0026#34;, \u0026#34;_ansible_item_result\u0026#34;: true, \u0026#34;_ansible_no_log\u0026#34;: false, \u0026#34;_ansible_parsed\u0026#34;: true, \u0026#34;changed\u0026#34;: false, \u0026#34;commands\u0026#34;: [], \u0026#34;failed\u0026#34;: false, \u0026#34;invocation\u0026#34;: { \u0026#34;module_args\u0026#34;: { \u0026#34;auth_pass\u0026#34;: null, \u0026#34;authorize\u0026#34;: null, \u0026#34;banner\u0026#34;: \u0026#34;login\u0026#34;, \u0026#34;host\u0026#34;: null, \u0026#34;password\u0026#34;: null, \u0026#34;port\u0026#34;: null, \u0026#34;provider\u0026#34;: null, \u0026#34;ssh_keyfile\u0026#34;: null, \u0026#34;state\u0026#34;: \u0026#34;present\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;===This device is being managed by Ansible===\\nMaking changes at your own risk!\\n\u0026#34;, \u0026#34;timeout\u0026#34;: null, \u0026#34;username\u0026#34;: null } }, \u0026#34;item\u0026#34;: \u0026#34;login\u0026#34; }, { \u0026#34;_ansible_ignore_errors\u0026#34;: null, \u0026#34;_ansible_item_label\u0026#34;: \u0026#34;exec\u0026#34;, \u0026#34;_ansible_item_result\u0026#34;: true, \u0026#34;_ansible_no_log\u0026#34;: false, \u0026#34;_ansible_parsed\u0026#34;: true, \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;banner exec @\\n===This device is being managed by Ansible===\\nMaking changes at your own risk!\\n@\u0026#34; ], \u0026#34;failed\u0026#34;: false, \u0026#34;invocation\u0026#34;: { \u0026#34;module_args\u0026#34;: { \u0026#34;auth_pass\u0026#34;: null, \u0026#34;authorize\u0026#34;: null, \u0026#34;banner\u0026#34;: \u0026#34;exec\u0026#34;, \u0026#34;host\u0026#34;: null, \u0026#34;password\u0026#34;: null, \u0026#34;port\u0026#34;: null, \u0026#34;provider\u0026#34;: null, \u0026#34;ssh_keyfile\u0026#34;: null, \u0026#34;state\u0026#34;: \u0026#34;present\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;===This device is being managed by Ansible===\\nMaking changes at your own risk!\\n\u0026#34;, \u0026#34;timeout\u0026#34;: null, \u0026#34;username\u0026#34;: null } }, \u0026#34;item\u0026#34;: \u0026#34;exec\u0026#34; } ] } } Summary #The ios_banner module is a quick and handy module for those that need to have banners as part of the operating entity. There are many reasons for banners that this is not going to explore further, there are plenty of other resources (including possible Legal ones) available for this discussion. Hopefully this has been a good primer of what things look like for the ios_banner and what output looks like.\n","date":"2019-02-10","permalink":"https://josh-v.com/ansible-ios-banner/","section":"Posts","summary":"\u003cp\u003eToday\u0026rsquo;s post is going to be a short and sweet one (unless I get to writing two). I\u0026rsquo;m going to take a\nlook at \u003ccode\u003eios_banner\u003c/code\u003e module. This one is pretty much straight to the point, what it states,\nmodifying the banner on an IOS device. There are multiple reasons to want to manipulate the banner\non a Cisco device. We will leave those reasons to you and the organization that you are a part of\nfor that. For now, we will take a real quick look at the module.\u003c/p\u003e","title":"Ansible IOS Banner"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/ios_banner/","section":"Tags","summary":"","title":"Ios_banner"},{"content":"In this post we will talk about primarily three components that will work together to get structured data out of the command line of a Cisco device. The three pieces are:\nAnsible Network Engine Google\u0026rsquo;s TextFSM Network to Code Templates Why this Post? #I\u0026rsquo;m writing this post because I was initially hesitant to start using the Ansible role originally when I was doing everything pretty well with the generic modules that come available with Ansible. I was challenged to migrate a Python script that was using TextFSM and Netmiko to be in Ansible. So I was originally aware of Ansible Network Engine, but had not done anything with it. So what better time than to put it to practice than when it is needed.\nAnsible Network Engine #The Ansible network engine is an Ansible role that is being developed by the Red Hat Ansible team. From the Github page:\nThis role provides the foundation for building network roles by providing modules and plug-ins that are common to all Ansible Network roles.\nWithin Ansible Network Engine you have the ability to parse the output of text. You can write your own parser, or leverage some work that has been done by others (and willing to put the work out for the good of the community - not stealing it).\nI\u0026rsquo;m going to recommend to read more on Ansible Network Engine parser with your own text parsing to go to this site - https://termlen0.github.io/2018/06/26/observations/ This post is more about using already existing TextFSM parsers with the help from NTC than the parser itself.\nA second link found recently is from the Ansible linklight (learning) team. Take a look here if wanting to do more with Parsers. https://github.com/ansible/workshops/tree/master/exercises/ansible_network/supplemental/3-1-parser\nTextFSM #From the Github page:\nTextFSM is a Python module which implements a template based state machine for parsing semi-formatted text. Originally developed to allow programmatic access to information returned from the command line interface (CLI) of networking devices.\nBasically the gist of things is that TextFSM takes text that is output from a show command and puts it into structured data. It does this using a regex pattern matching setup under the hood. This can be helpful for grabbing information out of a text blob issues to networking devices.\nYou must first install the textfsm python module for this to work. To install, I recommend installing on both Python2 and Python3 in case the Ansible version is still using Python2:\npip install textfsm pip3 install textfsm TextFSM Parser #Digging into the code on the Ansible Network Engine Github page you will find the file: https://github.com/ansible-network/network-engine/blob/devel/library/textfsm_parser.py\nThis is the TextFSM parser engine that is able to be leveraged. Looking at the Python file and the EXAMPLES section you can find much more information about how to leverage the particular module. From the Python file it has the following section:\n- name: store returned facts into a key call output textfsm_parser: file: files/parser_templates/show_interface.yaml content: \u0026#34;{{ lookup(\u0026#39;file\u0026#39;, \u0026#39;output/show_interfaces.txt\u0026#39;) }}\u0026#34; name: output Breaking this down further helps to get to the point.\nLine 1: This is the name of the Ansible task, nothing new here\nLine 2: Calls the plugin textfsm_parser\nLine 3: File, this is the parsing file that you are leveraging in the task, and where you would call the file location for the ntc template Line 4: Content, this is what you are going to send through the parser. In the example given it is a file, but you can also have a variable of say output from a previous command run put in here Line 5: name, this is where you will store the output data, it will be in a structured format\nThis will not get into reading the output of the structured data. For more on that please take a look back at my previous post on working without output\nThe original tricky part was the part about the File. Originally a lot of posts related to having a parser file all set to go. My original thinking was I don\u0026rsquo;t see those parsers, but they are in the examples. I decided to try to point the textfsm parser at an NTC template that had been downloaded. After this, success.\nContent is the text that you want to send through the parser. So a variable or a text file\nThe name portion is what you are registering as facts that can be accessed underneath ansible_facts.\nNTC Templates #NTC (Network to Code) has a community environment with a significant number of parsers available. I have found these particularly helpful in the Cisco environment. To install - checkout the Github page.\nhttps://github.com/networktocode/ntc-templates\nThese are files, so the best methodology I have found is to download these to your machine using the git process of cloning. There are updates made regularly, so make sure to do git pull to get the most recent version.\nPro tip: You may want to install these to your home directory. This is the default directory if memory serves me right that Netmiko will look for the textfsm templates as well\nSample #Here is the playbook that I will run against the lab environment.\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: switches gather_facts: no become: yes become_method: enable roles: - ansible-network.network-engine tasks: - name: CLI \u0026gt;\u0026gt; Get CDP neighbors ios_command: commands: - show cdp neighbors register: command_output - name: SYS \u0026gt;\u0026gt; Parse CDP Information textfsm_parser: file: \u0026#34;/opt/ntc-templates/templates/cisco_ios_show_cdp_neighbors.template\u0026#34; content: \u0026#34;{{ command_output.stdout[0] }}\u0026#34; name: cdp_facts - name: DEBUG \u0026gt;\u0026gt; Print output debug: msg: \u0026#34;{{ ansible_facts.cdp_facts }}\u0026#34; Task 1: Get CDP Neighbor information #This task is going to log in and get the CDP neighbor information from the device and register it to a fact command_output\nTask 2: Send through the parser #This is where the parser comes in, it will take the command output taken in the first task and send it through the textfsm parser. This then registers the information underneath ansible_facts. The next task is where you will see that output.\nTask 3: Prints the output #You will see the information underneath the ansible_facts to get at the information as it sits parsed.\nSample Output # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 PLAY [Switch config] ******************************************************************************* TASK [CLI \u0026gt;\u0026gt; Get CDP neighbors] ******************************************************************** ok: [sw01] TASK [SYS \u0026gt;\u0026gt; Parse CDP Information] **************************************************************** ok: [sw01] TASK [DEBUG \u0026gt;\u0026gt; Print output] *********************************************************************** ok: [sw01] =\u0026gt; { \u0026#34;msg\u0026#34;: [ { \u0026#34;CAPABILITY\u0026#34;: \u0026#34;R S\u0026#34;, \u0026#34;LOCAL_INTERFACE\u0026#34;: \u0026#34;Gig 1/0\u0026#34;, \u0026#34;NEIGHBOR\u0026#34;: \u0026#34;Switch\u0026#34;, \u0026#34;NEIGHBOR_INTERFACE\u0026#34;: \u0026#34;Gig 1/0\u0026#34;, \u0026#34;PLATFORM\u0026#34;: \u0026#34;I\u0026#34; }, { \u0026#34;CAPABILITY\u0026#34;: \u0026#34;R S\u0026#34;, \u0026#34;LOCAL_INTERFACE\u0026#34;: \u0026#34;Gig 1/1\u0026#34;, \u0026#34;NEIGHBOR\u0026#34;: \u0026#34;Switch\u0026#34;, \u0026#34;NEIGHBOR_INTERFACE\u0026#34;: \u0026#34;Gig 1/1\u0026#34;, \u0026#34;PLATFORM\u0026#34;: \u0026#34;I\u0026#34; }, { \u0026#34;CAPABILITY\u0026#34;: \u0026#34;R\u0026#34;, \u0026#34;LOCAL_INTERFACE\u0026#34;: \u0026#34;Gig 0/0\u0026#34;, \u0026#34;NEIGHBOR\u0026#34;: \u0026#34;router_edge\u0026#34;, \u0026#34;NEIGHBOR_INTERFACE\u0026#34;: \u0026#34;Gig 0/2\u0026#34;, \u0026#34;PLATFORM\u0026#34;: \u0026#34;B\u0026#34; } ] } PLAY RECAP ***************************************************************************************** sw01 : ok=3 changed=0 unreachable=0 failed=0 Summary #Putting all of these together into a playbook you can more easily get at information presented from a network device command line. Let\u0026rsquo;s say you wanted to get CDP neighbors. The CDP neighbor command output is tough to work with, other than seeing if something is in the output.\n","date":"2019-01-27","permalink":"https://josh-v.com/ansible-network-engine-ntc-templates/","section":"Posts","summary":"\u003cp\u003eIn this post we will talk about primarily three components that will work together to get structured\ndata out of the command line of a Cisco device. The three pieces are:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/ansible-network/network-engine\" target=\"_blank\" rel=\"noreferrer\"\u003eAnsible Network Engine\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/google/textfsm\" target=\"_blank\" rel=\"noreferrer\"\u003eGoogle\u0026rsquo;s TextFSM\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/networktocode/ntc-templates\" target=\"_blank\" rel=\"noreferrer\"\u003eNetwork to Code Templates\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"why-this-post\" class=\"relative group\"\u003eWhy this Post? \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#why-this-post\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eI\u0026rsquo;m writing this post because I was initially hesitant to start using the Ansible role originally\nwhen I was doing everything pretty well with the generic modules that come available with Ansible. I\nwas challenged to migrate a Python script that was using TextFSM and Netmiko to be in Ansible. So I\nwas originally aware of Ansible Network Engine, but had not done anything with it. So what better\ntime than to put it to practice than when it is needed.\u003c/p\u003e","title":"Ansible Network Engine and NTC Templates"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/ntc/","section":"Tags","summary":"","title":"Ntc"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/parsing/","section":"Tags","summary":"","title":"Parsing"},{"content":"This is a post that I\u0026rsquo;m going to review some of the differences between the ios_config module and the new cli_config module within Ansible networking. I became interested in the module after a recent discussion between the two. I have decided to take a look at the differences between the two.\nThis is not an under the hood look at the modules. This has already been covered very well (and with better graphics than I can produce) here at the Ansible Blog look for \u0026ldquo;cli_command and cli_config\u0026rdquo; with your browser find function.\nI may also try to take a look at some of the other modules as well as time may permit. Next up on my interest of is the NXOS commands. I may also be limited a touch on some of the other major platforms out there, but hopefully I can find some legitimately and provide some value back.\nDifferences #Parameters #First the differences come in a couple of front and center options. First, in cli_config there are a few more options to do with committing configurations. These play a role in having a \u0026ldquo;uniform\u0026rdquo; module for pushing to all sorts of devices like IOS, JUNOS, and the such.\nLines vs config\nOne of the major differences in the paramaters comes on how you put a configuration into the module. With the original ios_config you get to pass The ordered set of commands to the module. This means that you can apply multiple commands within one statement.\nWith cli_config you are passing a string into the module that is The config to be pushed to the network device.\nThis difference is a very important one. For instance if you wanted to apply multiple lines to a configuration you will need to find another way with cli_config that previously was very simple to read:\ntasks: - name: IOS \u0026gt;\u0026gt; No shut the interfaces ios_config: lines: - description ** Configured by Ansible ** - no shutdown parents: interface GigabitEthernet1/0 After doing a few different tests including using the | character to send multiple lines, \\n as a new line character, and using with_items all to no avail. Last step I tried to use the old carriage return \\r in the config at which point it was successful.\ntasks: - name: CLI \u0026gt;\u0026gt; No shut the interfaces cli_config: config: \u0026#34;interface Gig1/0\\rdescription **CLI Config!**\\rno shutdown\u0026#34; Templating #Templating is also a little different. From the main module page you can see an example that is the following:\nCLI Config # - name: configure device with config (CLI) cli_config: config: \u0026#34;{{ lookup(\u0026#39;template\u0026#39;, \u0026#39;basic/config.j2\u0026#39;) }}\u0026#34; IOS Config\n- name: configure device with config (IOS) ios_config: src: config.j2 So the only real difference is the lookup module used in the CLI version. This is pretty straight forward to see what it is doing. It is using the lookup filter, of type template. Then the 2nd argument is the template file that you wish to render.\nExecution Information #This is maybe the biggest difference that I have found between the ios_config module and the cli_config module. When storing results of the configuration module execution, you will only get back two fields - changed and failed. You will not be able to see what was executed that you can see with the ios_config module.\nLab Setup #The lab setup for this is pretty simple. I have added a Cisco IOS L2 switch image to the previous lab that I had in the previous post. This is really just for a device to connect to.\nI am configuring a port channel, only because that is something that I had lined up quick in the test, no other particular reason.\nThe Jinja2 template file that I am calling in this execution is the following:\n1 2 3 4 5 interface Port-channel5 switchport trunk allowed vlan 2,4,5 switchport trunk encapsulation dot1q switchport mode trunk spanning-tree portfast edge trunk Here is the playbook run with the CLI module:\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: switches gather_facts: no become: yes become_method: enable tags: [\u0026#39;switches\u0026#39;] tasks: - name: CLI \u0026gt;\u0026gt; Configure Port channel cli_config: config: \u0026#34;{{ lookup(\u0026#39;template\u0026#39;, \u0026#39;port_channel.j2\u0026#39;) }}\u0026#34; register: cli_output - name: DEBUG debug: msg: \u0026#34;{{ item }}\u0026#34; with_items: - \u0026#34;{{ cli_output }}\u0026#34; Output from this is:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 PLAY [Switch config] ******************************************************************************* TASK [CLI \u0026gt;\u0026gt; Configure Port channel] *************************************************************** changed: [sw01] TASK [DEBUG] *************************************************************************************** ok: [sw01] =\u0026gt; (item={\u0026#39;failed\u0026#39;: False, u\u0026#39;changed\u0026#39;: True}) =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: true, \u0026#34;failed\u0026#34;: false } } PLAY RECAP ***************************************************************************************** sw01 : ok=2 changed=1 unreachable=0 failed=0 Moving to virtually the same playbook here:\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Switch config connection: network_cli hosts: switches gather_facts: no become: yes become_method: enable tags: [\u0026#39;switches\u0026#39;] tasks: - name: IOS \u0026gt;\u0026gt; Configure port channel ios_config: src: port_channel.j2 register: ios_output - name: DEBUG debug: msg: \u0026#34;{{ item }}\u0026#34; with_items: - \u0026#34;{{ ios_output }}\u0026#34; The resulting output also includes banners, commands, and updates.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 PLAY [Switch config] *********************************************************** TASK [IOS \u0026gt;\u0026gt; Configure port channel] ******************************************* changed: [sw01] TASK [DEBUG] ******************************************************************* ok: [sw01] =\u0026gt; (item={\u0026#39;failed\u0026#39;: False, u\u0026#39;commands\u0026#39;: [u\u0026#39;interface Port-channel5\u0026#39;, u\u0026#39;switchport trunk allowed vlan 2,4,6\u0026#39;], u\u0026#39;changed\u0026#39;: True, u\u0026#39;updates\u0026#39;: [u\u0026#39;interf ace Port-channel5\u0026#39;, u\u0026#39;switchport trunk allowed vlan 2,4,6\u0026#39;], u\u0026#39;banners\u0026#39;: {}}) =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;banners\u0026#34;: {}, \u0026#34;changed\u0026#34;: true, \u0026#34;commands\u0026#34;: [ \u0026#34;interface Port-channel5\u0026#34;, \u0026#34;switchport trunk allowed vlan 2,4,6\u0026#34; ], \u0026#34;failed\u0026#34;: false, \u0026#34;updates\u0026#34;: [ \u0026#34;interface Port-channel5\u0026#34;, \u0026#34;switchport trunk allowed vlan 2,4,6\u0026#34; ] } } PLAY RECAP ********************************************************************* sw01 : ok=2 changed=1 unreachable=0 failed=0 ","date":"2019-01-12","permalink":"https://josh-v.com/ansible-cli-vs-ios-high-level/","section":"Posts","summary":"\u003cp\u003eThis is a post that I\u0026rsquo;m going to review some of the differences between the ios_config module and\nthe new cli_config module within Ansible networking. I became interested in the module after a\nrecent discussion between the two. I have decided to take a look at the differences between the two.\u003c/p\u003e\n\u003cp\u003eThis is not an under the hood look at the modules. This has already been covered very well (and\nwith better graphics than I can produce) here at the\n\u003ca href=\"https://www.ansible.com/blog/red-hat-ansible-network-automation-updates\" target=\"_blank\" rel=\"noreferrer\"\u003eAnsible Blog\u003c/a\u003e look for\n\u0026ldquo;cli_command and cli_config\u0026rdquo; with your browser find function.\u003c/p\u003e","title":"Ansible differences between ios config and cli config"},{"content":"You have decided to move forward with using/trying Ansible. You can now connect to a device and get a green success that you get a hello world like command such as show hostname or show inventory and get the GREEN success on Ansible. Now what. You may want to see the output of the command that you sent and got information back. This is your post on getting started.\nThis is the process that I typically go through when developing a playbook for use. Let\u0026rsquo;s say this is a playbook that you wish to just get show information out of the device, say investigating if there are any configurations that are applied that would be part of a CVE bug, or just operational status.\nDuring this post I will relate the Ansible data structures/formats to that of Python. So the terms will be dictionary (hashes) and lists (lists).\nPlaybook Design Process\nMake sure that I can connect to the devices with a simple show command Get necessary show output Debug the outputs of the show commands Set facts or take more action based on other outputs This can get extremely elaborate. I am going to attempt to keep this about the debug commands along the way.\nLab Setup #First I will just give a quick diagram of the lab environment. This is simulated with EVE-NG. I will be accessing the devices via a management network to show various things.\nI am going to connect to just Cisco IOS and Cisco ASA virtual images for this. That can extend as well to any other platform using the standard Ansible 2.9 network modules.\nPlaybook Play and Task #First, the initial connection and a simple show command.\nPlaybook A #Task 1 (A1) #The play in the playbook is going to log in and execute the following two tasks:\nTask 1: Issue command show run interface loopback 0 -\u0026gt; Save to a variable named show_commands Task 2: Debug the output of the variable show_commands, which is stored for each device that is connected to. --- # yamllint disable rule:truthy - name: Test command outputs connection: network_cli hosts: cisco_routers gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; Show commands ios_command: commands: - show run interface loopback 0 register: show_commands - name: SYS \u0026gt;\u0026gt; DEBUG OUTPUT debug: msg: \u0026#34;{{ show_commands }}\u0026#34; Here is the output from connecting to a single device:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 PLAY [Test command outputs] **************************************************** TASK [IOS \u0026gt;\u0026gt; Show commands] **************************************************** ok: [rtr01] TASK [SYS \u0026gt;\u0026gt; DEBUG OUTPUT] ***************************************************** ok: [rtr01] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;failed\u0026#34;: false, \u0026#34;stdout\u0026#34;: [ \u0026#34;Building configuration...\\n\\nCurrent configuration : 68 bytes\\n!\\ni nterface Loopback0\\n ip address 10.100.100.1 255.255.255.255\\nend\u0026#34; ], \u0026#34;stdout_lines\u0026#34;: [ [ \u0026#34;Building configuration...\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;Current configuration : 68 bytes\u0026#34;, \u0026#34;!\u0026#34;, \u0026#34;interface Loopback0\u0026#34;, \u0026#34; ip address 10.100.100.1 255.255.255.255\u0026#34;, \u0026#34;end\u0026#34; ] ] } } PLAY RECAP ********************************************************************* rtr01 : ok=2 changed=0 unreachable=0 failed=0 As we look at the output, the task itself creates the part \u0026quot;msg\u0026quot;: This itself shows the output dictionary. This has several keys: and values. Breaking down each of the keys and values in the output:\nchanged: This is a boolean field where you will see if the variable stored (output of a task) had made a change. Most *_command outputs will not make changes to the devices. failed: Did the task have a failed return code or not stdout: The standard output, including escape characters, of the output, this output is a list stdout_lines: This is a more human readable output format, that puts the line breaks in. This output is in the format of a list These outputs are in the forms of lists, so if we want to get access to the actual string of the command show run interface loopback 0? We need to access the show_commands['stdout'][0]. This will be shown with the updated playbook (a second debug has been added):\n--- # yamllint disable rule:truthy - name: Test command outputs connection: network_cli hosts: cisco_routers gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; Show commands ios_command: commands: - show run interface loopback 0 register: show_commands - name: SYS \u0026gt;\u0026gt; DEBUG OUTPUT debug: msg: \u0026#34;{{ show_commands }}\u0026#34; - name: SYS \u0026gt;\u0026gt; DEBUG to get to the actual output debug: msg: \u0026#34;{{ show_commands[\u0026#39;stdout\u0026#39;][0] }}\u0026#34; This now yields this output:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 PLAY [Test of connectivity] **************************************************** TASK [IOS \u0026gt;\u0026gt; Show commands] **************************************************** ok: [rtr01] TASK [SYS \u0026gt;\u0026gt; DEBUG OUTPUT] ***************************************************** ok: [rtr01] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;failed\u0026#34;: false, \u0026#34;stdout\u0026#34;: [ \u0026#34;Building configuration...\\n\\nCurrent configuration : 68 bytes\\n!\\ni nterface Loopback0\\n ip address 10.100.100.1 255.255.255.255\\nend\u0026#34; ], \u0026#34;stdout_lines\u0026#34;: [ [ \u0026#34;Building configuration...\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;Current configuration : 68 bytes\u0026#34;, \u0026#34;!\u0026#34;, \u0026#34;interface Loopback0\u0026#34;, \u0026#34; ip address 10.100.100.1 255.255.255.255\u0026#34;, \u0026#34;end\u0026#34; ] ] } } TASK [SYS \u0026gt;\u0026gt; DEBUG to get to the actual output] ******************************** ok: [rtr01] =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;Building configuration...\\n\\nCurrent configuration : 68 bytes\\n!\\nin terface Loopback0\\n ip address 10.100.100.1 255.255.255.255\\nend\u0026#34; } PLAY RECAP ********************************************************************* rtr01 : ok=3 changed=0 unreachable=0 failed=0 Why Lists? #So why are stdout and stdout_lines are in the type of lists? This goes back to the section of the Ansible module ios_commands where commands is fed a list. This means that you can send multiple commands in a single task. Updating the playbook to be this:\n--- # yamllint disable rule:truthy - name: Test command outputs connection: network_cli hosts: cisco_routers gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; Show commands ios_command: commands: - show run interface loopback 0 - ping 8.8.8.8 source loopback 0 repeat 20 size 1500 register: show_commands - name: SYS \u0026gt;\u0026gt; DEBUG OUTPUT debug: msg: \u0026#34;{{ show_commands }}\u0026#34; - name: SYS \u0026gt;\u0026gt; DEBUG to get to the actual output for 1st command run debug: msg: \u0026#34;{{ show_commands[\u0026#39;stdout\u0026#39;][0] }}\u0026#34; - name: SYS \u0026gt;\u0026gt; DEBUG to get the ping results debug: msg: \u0026#34;{{ show_commands[\u0026#39;stdout\u0026#39;][1] }}\u0026#34; Which now yields:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 PLAY [Test command outputs] **************************************************** TASK [IOS \u0026gt;\u0026gt; Show commands] **************************************************** ok: [rtr01] TASK [SYS \u0026gt;\u0026gt; DEBUG OUTPUT] ***************************************************** ok: [rtr01] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;failed\u0026#34;: false, \u0026#34;stdout\u0026#34;: [ \u0026#34;Building configuration...\\n\\nCurrent configuration : 68 bytes\\n!\\ninterface Loopback0\\n ip address 10.100.100.1 255.255.255.255\\nend\u0026#34;, \u0026#34;Type escape sequence to abort.\\nSending 20, 1500-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds:\\nPacket sent with a source address of 10.100.100.1 \\n!!!!!!!!!!!!!!!!!!!!\\nSuccess rate is 100 percent (20/20), round-trip min/avg/max = 86/108/213 ms\u0026#34;, \u0026#34;Type escape sequence to abort.\\nSending 20, 1500-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds:\\nPacket sent with a source address of 10.100.100.1 \\n!!!!!!!!!!!!!!!!!!!!\\nSuccess rate is 100 percent (20/20), round-trip min/avg/max = 82/108/217 ms\u0026#34; ], \u0026#34;stdout_lines\u0026#34;: [ [ \u0026#34;Building configuration...\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;Current configuration : 68 bytes\u0026#34;, \u0026#34;!\u0026#34;, \u0026#34;interface Loopback0\u0026#34;, \u0026#34; ip address 10.100.100.1 255.255.255.255\u0026#34;, \u0026#34;end\u0026#34; ], [ \u0026#34;Type escape sequence to abort.\u0026#34;, \u0026#34;Sending 20, 1500-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds:\u0026#34;, \u0026#34;Packet sent with a source address of 10.100.100.1 \u0026#34;, \u0026#34;!!!!!!!!!!!!!!!!!!!!\u0026#34;, \u0026#34;Success rate is 100 percent (20/20), round-trip min/avg/max = 86/108/213 ms\u0026#34; ] ] } } TASK [SYS \u0026gt;\u0026gt; DEBUG to get to the actual output for 1st command run] *** ok: [rtr01] =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;Building configuration...\\n\\nCurrent configuration : 68 bytes\\n!\\nin terface Loopback0\\n ip address 10.100.100.1 255.255.255.255\\nend\u0026#34; } TASK [SYS \u0026gt;\u0026gt; DEBUG to get the ping results] ************************************ ok: [rtr01] =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;Type escape sequence to abort.\\nSending 20, 1500-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds:\\nPacket sent with a source address of 10.100.100. 1 \\n!!!!!!!!!!!!!!!!!!!!\\nSuccess rate is 100 percent (20/20), round-trip min/av g/max = 41/108/260 ms\u0026#34; } PLAY RECAP ********************************************************************* rtr01 : ok=4 changed=0 unreachable=0 failed=0 We can now see how you may get at particular command outputs, while running multiple commands during one task on the device. From what I can tell, these commands are run sequentially, and not with separate SSH sessions, as during my testing I only ever saw a single SSH session on the device.\nAccessing variables from other tasks #This is something that I stumbled upon at some point that was helpful in multiple play playbooks. You have multiple plays in a playbook right? So how do you get at information from a previous task? You access it via the keyword variable hostvars. I\u0026rsquo;ve added a second play to the previous playbook. I also added another DNS provider to test my pings to in order to show this.\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Test command outputs connection: network_cli hosts: cisco_routers gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; Show commands ios_command: commands: - show run interface loopback 0 - ping 8.8.8.8 source loopback 0 repeat 20 size 1500 - ping 1.1.1.1 source loopback 0 repeat 20 size 1500 register: show_commands - name: SYS \u0026gt;\u0026gt; DEBUG OUTPUT debug: msg: \u0026#34;{{ show_commands }}\u0026#34; - name: SYS \u0026gt;\u0026gt; DEBUG to get to the actual output for 1st command run debug: msg: \u0026#34;{{ show_commands[\u0026#39;stdout\u0026#39;][0] }}\u0026#34; - name: SYS \u0026gt;\u0026gt; DEBUG to get the ping results debug: msg: \u0026#34;{{ show_commands[\u0026#39;stdout\u0026#39;][1] }}\u0026#34; - name: See output from previous play connection: local hosts: local gather_facts: no tasks: - name: SYS \u0026gt;\u0026gt; Debug variable from previous task debug: msg: \u0026#34;{{ hostvars[\u0026#39;rtr01\u0026#39;][\u0026#39;show_commands\u0026#39;][\u0026#39;stdout\u0026#39;][2] }}\u0026#34; This now has the output of the ping test to 1.1.1.1 in the output.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 PLAY [Test command outputs] **************************************************** ~~~~ PLAY OUTPUT TRUNCATED FOR BREVITY ~~~~~ PLAY [See output from previous play] ******************************************* TASK [SYS \u0026gt;\u0026gt; Debug variable from previous task] ******************************** ok: [localhost] =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;Type escape sequence to abort.\\nSending 20, 1500-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds:\\nPacket sent with a source address of 10.100.100. 1 \\n!!!!!!!!!!!!!!!!!!!!\\nSuccess rate is 100 percent (20/20), round-trip min/av g/max = 63/108/236 ms\u0026#34; } PLAY RECAP ********************************************************************* localhost : ok=1 changed=0 unreachable=0 failed=0 rtr01 : ok=4 changed=0 unreachable=0 failed=0 Looping over the output #You can also loop over the output of the commands as well. I added in some more lines to the debug that will show how you can loop over all of the commands you issued. In a future post we will discuss on how to debug through using with_items.\n1 2 3 4 - name: SYS \u0026gt;\u0026gt; DEBUG to see loop debug: msg: \u0026#34;{{ item }}\u0026#34; with_items: \u0026#34;{{ show_commands[\u0026#39;stdout\u0026#39;] }}\u0026#34; We want to get to each of the stdout outputs. I\u0026rsquo;ve added with_items and we have a new variable of item that we reference in the message. We now get the following output related to that task:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 TASK [SYS \u0026gt;\u0026gt; DEBUG to see loop] ************************************************ ok: [rtr01] =\u0026gt; (item=Building configuration... Current configuration : 68 bytes ! interface Loopback0 ip address 10.100.100.1 255.255.255.255 end) =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;Building configuration...\\n\\nCurrent configuration : 68 bytes\\n!\\nin terface Loopback0\\n ip address 10.100.100.1 255.255.255.255\\nend\u0026#34; } ok: [rtr01] =\u0026gt; (item=Type escape sequence to abort. Sending 20, 1500-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds: Packet sent with a source address of 10.100.100.1 !!!!!!!!!!!!!!!!!!!! Success rate is 100 percent (20/20), round-trip min/avg/max = 86/108/213 ms) =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;Type escape sequence to abort.\\nSending 20, 1500-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds:\\nPacket sent with a source address of 10.100.10 0.1 \\n!!!!!!!!!!!!!!!!!!!!\\nSuccess rate is 100 percent (20/20), round-trip min /avg/max = 86/108/213 ms\u0026#34; } ok: [rtr01] =\u0026gt; (item=Type escape sequence to abort. Sending 20, 1500-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds: Packet sent with a source address of 10.100.100.1 !!!!!!!!!!!!!!!!!!!! Success rate is 100 percent (20/20), round-trip min/avg/max = 82/108/217 ms) =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;Type escape sequence to abort.\\nSending 20, 1500-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds:\\nPacket sent with a source address of 10.100.10 0.1 \\n!!!!!!!!!!!!!!!!!!!!\\nSuccess rate is 100 percent (20/20), round-trip min /avg/max = 82/108/217 ms\u0026#34; } Final Run #Here is the final playbook\n--- # yamllint disable rule:truthy # yamllint disable rule:line-length - name: Test command outputs connection: network_cli hosts: cisco_routers gather_facts: no become: yes become_method: enable tasks: - name: IOS \u0026gt;\u0026gt; Show commands ios_command: commands: - show run interface loopback 0 - ping 8.8.8.8 source loopback 0 repeat 20 size 1500 - ping 1.1.1.1 source loopback 0 repeat 20 size 1500 register: show_commands - name: SYS \u0026gt;\u0026gt; DEBUG OUTPUT debug: msg: \u0026#34;{{ show_commands }}\u0026#34; - name: SYS \u0026gt;\u0026gt; DEBUG to get to the actual output for 1st command run debug: msg: \u0026#34;{{ show_commands[\u0026#39;stdout\u0026#39;][0] }}\u0026#34; - name: SYS \u0026gt;\u0026gt; DEBUG to get the ping results debug: msg: \u0026#34;{{ show_commands[\u0026#39;stdout\u0026#39;][1] }}\u0026#34; - name: SYS \u0026gt;\u0026gt; DEBUG to see loop debug: msg: \u0026#34;{{ item }}\u0026#34; with_items: \u0026#34;{{ show_commands[\u0026#39;stdout\u0026#39;] }}\u0026#34; - name: See output from previous play connection: local hosts: local gather_facts: no tasks: - name: SYS \u0026gt;\u0026gt; Debug variable from previous task debug: msg: \u0026#34;{{ hostvars[\u0026#39;rtr01\u0026#39;][\u0026#39;show_commands\u0026#39;][\u0026#39;stdout\u0026#39;][2] }}\u0026#34; Final Run Output\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 PLAY [Test command outputs] **************************************************** TASK [IOS \u0026gt;\u0026gt; Show commands] **************************************************** ok: [rtr01] TASK [SYS \u0026gt;\u0026gt; DEBUG OUTPUT] ***************************************************** ok: [rtr01] =\u0026gt; { \u0026#34;msg\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;failed\u0026#34;: false, \u0026#34;stdout\u0026#34;: [ \u0026#34;Building configuration...\\n\\nCurrent configuration : 68 bytes\\n!\\ni nterface Loopback0\\n ip address 10.100.100.1 255.255.255.255\\nend\u0026#34;, \u0026#34;Type escape sequence to abort.\\nSending 20, 1500-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds:\\nPacket sent with a source address of 10.100.100 .1 \\n!!!!!!!!!!!!!!!!!!!!\\nSuccess rate is 100 percent (20/20), round-trip min/a vg/max = 86/108/213 ms\u0026#34;, \u0026#34;Type escape sequence to abort.\\nSending 20, 1500-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds:\\nPacket sent with a source address of 10.100.100 .1 \\n!!!!!!!!!!!!!!!!!!!!\\nSuccess rate is 100 percent (20/20), round-trip min/a vg/max = 82/108/217 ms\u0026#34; ], \u0026#34;stdout_lines\u0026#34;: [ [ \u0026#34;Building configuration...\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;Current configuration : 68 bytes\u0026#34;, \u0026#34;!\u0026#34;, \u0026#34;interface Loopback0\u0026#34;, \u0026#34; ip address 10.100.100.1 255.255.255.255\u0026#34;, \u0026#34;end\u0026#34; ], [ \u0026#34;Type escape sequence to abort.\u0026#34;, \u0026#34;Sending 20, 1500-byte ICMP Echos to 8.8.8.8, timeout is 2 secon ds:\u0026#34;, \u0026#34;Packet sent with a source address of 10.100.100.1 \u0026#34;, \u0026#34;!!!!!!!!!!!!!!!!!!!!\u0026#34;, \u0026#34;Success rate is 100 percent (20/20), round-trip min/avg/max = 8 6/108/213 ms\u0026#34; ], [ \u0026#34;Type escape sequence to abort.\u0026#34;, \u0026#34;Sending 20, 1500-byte ICMP Echos to 1.1.1.1, timeout is 2 secon ds:\u0026#34;, \u0026#34;Packet sent with a source address of 10.100.100.1 \u0026#34;, \u0026#34;!!!!!!!!!!!!!!!!!!!!\u0026#34;, \u0026#34;Success rate is 100 percent (20/20), round-trip min/avg/max = 8 2/108/217 ms\u0026#34; ] ] } } TASK [SYS \u0026gt;\u0026gt; DEBUG to get to the actual output for first command of show run] *** ok: [rtr01] =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;Building configuration...\\n\\nCurrent configuration : 68 bytes\\n!\\nint erface Loopback0\\n ip address 10.100.100.1 255.255.255.255\\nend\u0026#34; } TASK [SYS \u0026gt;\u0026gt; DEBUG to get the ping results] ************************************ ok: [rtr01] =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;Type escape sequence to abort.\\nSending 20, 1500-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds:\\nPacket sent with a source address of 10.100.100 .1 \\n!!!!!!!!!!!!!!!!!!!!\\nSuccess rate is 100 percent (20/20), round-trip min/ avg/max = 86/108/213 ms\u0026#34; } TASK [SYS \u0026gt;\u0026gt; DEBUG to see loop] ************************************************ ok: [rtr01] =\u0026gt; (item=Building configuration... Current configuration : 68 bytes ! interface Loopback0 ip address 10.100.100.1 255.255.255.255 end) =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;Building configuration...\\n\\nCurrent configuration : 68 bytes\\n!\\nin terface Loopback0\\n ip address 10.100.100.1 255.255.255.255\\nend\u0026#34; } ok: [rtr01] =\u0026gt; (item=Type escape sequence to abort. Sending 20, 1500-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds: Packet sent with a source address of 10.100.100.1 !!!!!!!!!!!!!!!!!!!! Success rate is 100 percent (20/20), round-trip min/avg/max = 86/108/213 ms) =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;Type escape sequence to abort.\\nSending 20, 1500-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds:\\nPacket sent with a source address of 10.100.10 0.1 \\n!!!!!!!!!!!!!!!!!!!!\\nSuccess rate is 100 percent (20/20), round-trip min /avg/max = 86/108/213 ms\u0026#34; } ok: [rtr01] =\u0026gt; (item=Type escape sequence to abort. Sending 20, 1500-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds: Packet sent with a source address of 10.100.100.1 !!!!!!!!!!!!!!!!!!!! Success rate is 100 percent (20/20), round-trip min/avg/max = 82/108/217 ms) =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;Type escape sequence to abort.\\nSending 20, 1500-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds:\\nPacket sent with a source address of 10.100.100 .1 \\n!!!!!!!!!!!!!!!!!!!!\\nSuccess rate is 100 percent (20/20), round-trip min/ avg/max = 82/108/217 ms\u0026#34; } PLAY [See output from previous play] ******************************************* TASK [SYS \u0026gt;\u0026gt; Debug variable from previous task] ******************************** ok: [localhost] =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;Type escape sequence to abort.\\nSending 20, 1500-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds:\\nPacket sent with a source address of 10.100.100. 1 \\n!!!!!!!!!!!!!!!!!!!!\\nSuccess rate is 100 percent (20/20), round-trip min/av g/max = 82/108/217 ms\u0026#34; } PLAY RECAP ********************************************************************* localhost : ok=1 changed=0 unreachable=0 failed=0 rtr01 : ok=5 changed=0 unreachable=0 failed=0 Hope that this has been helpful!\n","date":"2019-01-05","permalink":"https://josh-v.com/ansible-output-work/","section":"Posts","summary":"\u003cp\u003eYou have decided to move forward with using/trying Ansible. You can now connect to a device and get\na green success that you get a \u003cem\u003ehello world\u003c/em\u003e like command such as \u003ccode\u003eshow hostname\u003c/code\u003e or\n\u003ccode\u003eshow inventory\u003c/code\u003e and get the GREEN success on Ansible. Now what. You may want to see the output of\nthe command that you sent and got information back. This is your post on \u003cem\u003egetting started\u003c/em\u003e.\u003c/p\u003e\n\u003cp\u003eThis is the process that I typically go through when developing a playbook for use. Let\u0026rsquo;s say this\nis a playbook that you wish to just get show information out of the device, say investigating if\nthere are any configurations that are applied that would be part of a CVE bug, or just operational\nstatus.\u003c/p\u003e","title":"Ansible - Working with command output"},{"content":"Discontiguous masks are something that is going to be somewhat historic within the network design toolbox. It is basically a methodology of looking at particular bits of a network/host definition. The big thing to recall is that as a packet crosses a network device it does so within a packet. The packet is nothing more than a stream of bits. Within the packet header there are bits that define the source network address and the destination network address. This is where discontiguous masks come into play. With a system that can leverage discontiguous masks, you can access information about any part of the network bits, not just starting reading and then stopping (or vice versa) when you look at a bit boundry masking only.\nThis is a originally posted from my previous blog here Previous Post. I plan to give this a complete re-write when I have the opportunity.\nOriginal post #Simply put, discontiguous masks are those that are represented by a potentially alternating sets of 1s and 0s within a mask (subnet/standard mask or wild card). This does not really apply to a subnet mask as you have a network portion of the address, and then the host portion, so everything is contiguous. The primary place that you will see this is in Access Control Lists or ACLs (for more info on ACLs, here is a quick link to a Wikipedia article. This may be a topic later on within this blog http://en.wikipedia.org/wiki/Standard_Access_Control_List).\nThe first two use cases that these come in handy for are for Quality of Service ACLs and Security/Firewall ACLs. There are many more uses out there, but these are the two primary ones that I have come across. Feel free to leave comments, and I will dive into it, modifying this post for this.\nMy first tip for understanding this concept is that you need to think the way that a router (or other network device) thinks. Everything that passes through a router or switch is passing through the device in a logical method. The router and switch are looking at binary 1s and 0s as they cross the wire.\nEverything for this discussion revolves around binary addressing. Remember that currently in the IPv4 space that there are 32 bits to a network address. That is the IP address 192.168.0.1 can be translated into binary bits of 1100 0000 . 1010 1000 . 0000 0000 . 0000 0001.\nDiscontiguous masks are as they state, a mask that can be discontiguous. Many devices in the industry have support for what is a contiguous mask. This does not have do with wild card or subnet masking yet. It just matters how the devices check to see if something matches. In a standard mask or subnet mask, the mask that you define may look something like 255.255.255.0 (11111111.11111111.11111111.00000000). In this case you will be doing a logical \u0026ldquo;AND\u0026rdquo; operation where the bits that you care about are represented by a \u0026ldquo;1\u0026rdquo;. The bits that are represented by a \u0026ldquo;0\u0026rdquo; are those that you do not care about. In the Cisco world of wild card masking, it is the inverse. Most of my examples will be in wild card notation, since this is where it is most prevalent in my world.\nA discontiguous mask does not follow this. First, let\u0026rsquo;s take the previous example of the mask 255.255.255.0 and turn it into a wild card mask, so that we can follow along in the same fashion moving forward. This then turns into a 0.0.0.255 mask. Simply changing the important bits, or the bits that you care about from a \u0026ldquo;1\u0026rdquo; to a \u0026ldquo;0\u0026rdquo; and vice versa. Binary representation is 00000000.00000000.00000000.11111111. So now, the discontiguous part. Let\u0026rsquo;s say you run a network where you have 10 sites. They are all the same IP address wise, except that you change the third octet to represent your site. Let\u0026rsquo;s use addressing 192.168.0.0 - 192.168.255.255 to represent this. Site A has the IP addresses 192.168.1.0/24 (contiguous masking) and site B has the IP addresses 192.168.2.0/24. You assign a server the address of X.X.X.2 at each site. Now you want to write an ACL that will match that at each site.\nThe process that I would follow, is we need to figure out what is important. As we look at the 10 site addresses (we will just use two, but we will see the commonality) that we care about.\nIP address Binary 192.168.1.2 1100 0000 . 1010 1000 . 0000 0001 . 0000 0010 192.168.2.2 1100 0000 . 1010 1000 . 0000 0010 . 0000 0010 192.168.3.2 1100 0000 . 1010 1000 . 0000 0011 . 0000 0010 192.168.4.2 1100 0000 . 1010 1000 . 0000 0100 . 0000 0010 192.168.5.2 1100 0000 . 1010 1000 . 0000 0101 . 0000 0010 192.168.6.2 1100 0000 . 1010 1000 . 0000 0110 . 0000 0010 192.168.7.2 1100 0000 . 1010 1000 . 0000 0111 . 0000 0010 192.168.8.2 1100 0000 . 1010 1000 . 0000 1000 . 0000 0010 192.168.9.2 1100 0000 . 1010 1000 . 0000 1001 . 0000 0010 192.168.10.2 1100 0000 . 1010 1000 . 0000 1010 . 0000 0010 You notice a little bit of a pattern develop. So if I were to look at a wild card mask here, let\u0026rsquo;s look at each individual octet one at a time. I encourage you to do so when trying to create your own. We know that in this world that all of the addresses will start with 192.168. on all of the sites. This makes the first two octets very easy to write the wild card mask. You care about all of the bits in the first two octets. So they are simply \u0026ldquo;0\u0026rdquo;. So far we have 0.0.?.? for a mask.\nThe third octet is where it becomes more difficult. As we take a look at the binary information above, you will notice that there isn\u0026rsquo;t a complete pattern in that octet (with only 10 sites, there is a little bit of a pattern, but if this were to get expanded out to say 100 or 200 sites with each the same addressing, then it becomes more difficult. We will say for our discussion, that we don\u0026rsquo;t care what the third octet is. It can be 2, it could be 254 or 140. For simplicity sake of discontiguous explanation, this is the case, we don\u0026rsquo;t care about any of the bits. So now we would have the mask 0.0.255.?\nThe last octet is once again easy in this case. We care about every bit. So it is \u0026ldquo;0\u0026rdquo; again. Our complete wild card mask would be 0.0.255.0 in this case. This is where discontiguous masks are very powerful. Now, let\u0026rsquo;s say that we add a second server at each site. The easiest thing to do from a network perspective is set the IP address to .3 at the site. These two servers do the same functionality in a high availability design, so you want all traffic to be treated as equal. You simply change the very last bit in the octet to be a 1 (in that you don\u0026rsquo;t care if it is a 0 or a 1), and you have your new wild card mask of 0.0.255.1 that will match traffic on two IP addresses at a site instead of just one.\nIn a contiguous state, there would need to be a clear \u0026ldquo;border\u0026rdquo; between the 0s and 1s, that you can\u0026rsquo;t go back and forth on. So 0000000000000001111111111111111 is a contiguous mask. The 000000000000000011111111000000000 mask would not play well with hardware that does not support it.\nRemember, think about how a router thinks in order to understand clearly what is happening.\nHappy masking!\n","date":"2018-12-08","permalink":"https://josh-v.com/discontiguous_masks/","section":"Posts","summary":"\u003cp\u003eDiscontiguous masks are something that is going to be somewhat historic within the network design\ntoolbox. It is basically a methodology of looking at particular bits of a network/host definition.\nThe big thing to recall is that as a packet crosses a network device it does so within a packet. The\npacket is nothing more than a stream of bits. Within the packet header there are bits that define\nthe source network address and the destination network address. This is where discontiguous masks\ncome into play. With a system that can leverage discontiguous masks, you can access information\nabout any part of the network bits, not just starting reading and then stopping (or vice versa) when\nyou look at a bit boundry masking only.\u003c/p\u003e","title":"Discontiguous Masks"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/network-design/","section":"Tags","summary":"","title":"Network Design"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/segmentation/","section":"Tags","summary":"","title":"Segmentation"},{"content":"In a recent podcast there was some discussion that it sounded like the term Micro Segmentation was being used where it was really traditional segmentation. So I thought I would put out a few thoughts on this front.\nWhat is Segmnentation in Networking #Segmentation is a methodology to create separatet zones of sorts of various traffic types. Various places you may want to do this is within a campus environment to separate students from faculty, or engineering from finance. The list of examples goes on and on. Go to a basic reading of VLANs and you will get the idea of what segmentation is. Once you have VLANs, really segmentation then builds upon this and allows policy to be applied. This policy can be whether or not hosts should be able to talk to each other, or various traffic treatments (QoS). This is something that is well covered already and I do not wish to cover more.\nWhat is Micro Segmentation? #So the newer term is Micro Segmentation. This is exactly as what was covered before but doing it at an even more detailed level. This is getting into being able to apply policy to individual hosts within a segment, creating a micro-segmentation effort.\nIn Practice # Source Host Destination Host Able to apply policy - Segmentation Able to apply policy - Micro Segmentation Host A Host B No Yes Host A Host C No Yes Host A Host D Yes, at that L3 device/firewall Yes Host B Host C No Yes Host B Host D Yes, at that L3 device/firewall Yes Host C Host D Yes, at that L3 device/firewall Yes Given the diagram above of two network segments with a firewall in between, we will cover what you can enforce policy with and what you can\u0026rsquo;t. In this drawing this is a firewall that separates the network segments. However, you could also have this be a L3 device, such as a L3 switch, or a router where there are ACLs in place.\nWith traditional segmentation, you can apply policy only between the two segments. Host D will have policy applied to try to talk with Host A, B, or C. Host A, B, and C will be able to communicate between each other without any policy being applied.\nMicro Segmentation #In that same diagram with Micro Segmentation practices applied, not only can you have policy applied between Host D and the trio of hosts (A, B, C), but you can also apply policy between hosts A, B, C on the same network segment. So if you wanted to lock down so that Host A can only talk out of the segment, but not to any other hosts within the same segment, this is possible. This is the major difference between standard Segmentation and Micro Segmentation, the ability to prevent east-west traffic between hosts in the same network segments.\nHow to? #So how is this done you may ask? There may be more ways about this, but the ones that I\u0026rsquo;m aware of are:\nController Based Wireless (Preventing host to host communication) Cisco ISE Cisco ACI Private VLANs (Administratively burdensome) NSX Host based firewalls (More burden) (Maybe Illumio can help here? - Packet Pushers BIB Illumio) Does this work? #Absolutely. I have done host to host policy enforcement within a VLAN using Cisco ISE in a campus LAN environment. There was a requirement to prevent one host to talk to any other host within the same network segment. Cisco ISE definitely delivered on this, within the policy matrix by defining policy that a tag (say 6) could not talk to that same tag (6). If you have some unmanaged switches in the wire that don\u0026rsquo;t have the hosts wired up directly to a switchport, this is no longer feasible.\nSummary #So in summary, hopefully this helps clear up some ideas about what Segmentation and Micro Segmentation are in the industry. This is my take on what the difference between Segmentation and Micro Segmentation and that there is a difference. Not just segmenting at a L3 boundry.\nDiscussion #I have not figured out yet how to add disqus to the blog at this point. Hopefully I can get that added soon.\nNotes #Drawing completed at draw.io.\n","date":"2018-12-08","permalink":"https://josh-v.com/microsegmentation/","section":"Posts","summary":"\u003cp\u003eIn a recent podcast there was some discussion that it sounded like the term \u003cstrong\u003eMicro Segmentation\u003c/strong\u003e\nwas being used where it was really traditional \u003cstrong\u003esegmentation\u003c/strong\u003e. So I thought I would put out a few\nthoughts on this front.\u003c/p\u003e\n\u003ch2 id=\"what-is-segmnentation-in-networking\" class=\"relative group\"\u003eWhat is Segmnentation in Networking \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#what-is-segmnentation-in-networking\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003eSegmentation is a methodology to create separatet \u003cem\u003ezones\u003c/em\u003e of sorts of various traffic types. Various\nplaces you may want to do this is within a campus environment to separate students from faculty, or\nengineering from finance. The list of examples goes on and on. Go to a basic reading of VLANs and\nyou will get the idea of what segmentation is. Once you have VLANs, really segmentation then builds\nupon this and allows policy to be applied. This policy can be whether or not hosts should be able to\ntalk to each other, or various traffic treatments (QoS). This is something that is well covered\nalready and I do not wish to cover more.\u003c/p\u003e","title":"Micro Segmentation vs Segmentation"},{"content":"Life always gets busy. That is one thing that you always hear about. I am definitely in that boat as well that things are getting busy. With this in mind, I have never felt better about my ability to learn new things in the field. I do a few things that I feel are probably unique that I should share.\nThe first opportunity that has never been more capable of doing is to listen to podcasts. The commuting time to/from work is a tremendous opportunity. I leverage three podcasts as my primary learning mechanism these days.\nPodcasts #Packet Pushers #The first go to podcast is Packet Pushers. They have several different channels available, and my preference is the Fat Pipe podcast feed. This gets you a few different shows, that includes The Network Break, Weekly Show, and Datanaughts. There are more shows being added all of the time based on a criteria.\nThere is a membership system available called Ignition at their site. They also have a paid opportunity to help contribute back to the hosts and other premium content. Ideally I will be budgeting for multiple memberships, this being one of them. I just have not done this quite yet. This comes in at $99/year\nThe Network Collective #Network Collective is a recent addition and I look forward to the podcasts when it shows up in the feed. So far there are a couple of shows available. The podcast has a several list of hosts including Jordan Martin, Russ White, and Eyvonne Sharp. They have quite the variety of topics that constantly peak my interest. The down side of this is the annual cost for the premium access IMO, sitting at $250/year.\nHistory of Networking #Perhaps one of the premier shows of The Network Collective podcast network is the History of Networking series. The show brings on notable guests in the history of the development of the networking field as we know it today. This is a must check out regardless if there are other podcast series on this network.\nZigbits Network Design Podcast #This is a new one to me as of 2018. Quickly a favorite of mine as well. This podcast emphesis is on network design, and brings a whole differnet view than the Packet Pushers or The Network Collective. From episodes including CI/CD, Cisco ISE, or DevOps, this is a deeper podcast and much deeper topics, which is a must checkout.\nSlack Channels #This is one of my other favorites and perhaps the newest methodology. It is really a new take on an old format, IRC. Slack has created a place that using Markdown (sensing a theme) is prevelant to create a community of users. The first channel that I joined in the Network Automation spirit was that of NetworkToCode. You have to put a request to join, and once in you are welcomed kindly. If getting into using Slack, or any of the modern chat applications, I strongly recommend learning what Markdown can do.\nThe first two podcast networks that were mentioned of Packet Pushers and The Network Collective each have their own slack channels. Packet Pushers slack channel is free to join while The Network Collective has Slack as one of the membership benefits that is included in the $250 annual membership.\nTwitter #Social media is the thing that seems to be either very hot and very cold. A lot of good inspirational group of engineers in the field of Network Automation find themselves on Twitter. Yes, there is a negative stigma around social media, but I do recommed at least following a good group of people that are influential in Network Automation arena. I follow the hosts of the podcast networks listed above, a group of individuals putting out good work within Python and Ansible networking.\nHome Lab #The last, and maybe the least of those mentioned, is a home lab. I have gone back and forth from having actual server hardware running, to removing the hardware. I\u0026rsquo;ve had network lab gear up and running as well, and that is for the most part retired. I am now looking at a home lab that is having virtualized platforms running on a desktop and a few Raspberry Pi pieces hanging around.\nThe next iteration of a lab for myself will likely include a cloud environment as well. I\u0026rsquo;m inspired by the podcasts that Nicholas Russo (https://twitter.com/nickrusso42518) has put together within the Zigbits Network Design Podcast. He recently talked about his home lab not being powered up for a couple of years as he moved to AWS for his development work. I\u0026rsquo;ll encourage you to listen to the podcast linked on the Podcasts and Links page.\n","date":"2018-11-24","permalink":"https://josh-v.com/keeping-up-on-tech/","section":"Posts","summary":"\u003cp\u003eLife always gets busy. That is one thing that you always hear about. I am definitely in that boat as well that things are getting busy. With this in mind, I have never felt better about my ability to learn new things in the field. I do a few things that I feel are probably unique that I should share.\u003c/p\u003e\n\u003cp\u003eThe first opportunity that has never been more capable of doing is to listen to podcasts. The commuting time to/from work is a tremendous opportunity. I leverage three podcasts as my primary learning mechanism these days.\u003c/p\u003e","title":"Keeping Up on Tech"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/learning/","section":"Tags","summary":"","title":"Learning"},{"content":"Why this post? Because I decided to change the style of how I was hosting my blog. Before I had decided to just host the blog on something that was easy to get to and update. I could have kept on blogging there, but I found making blog posts a little bit more difficult than what I wanted to. I also wanted to learn some of the new ways of doing things within networking technologies.\nWith this, I decided to bring my blog over to a static site generator. I\u0026rsquo;m not doing anything significantly crazy with a blog site, other than hopefully creating some useful content. So static site generation brought me over to Github.\nWhat I wanted to accomplish #What I wanted to accomplish with my blog:\nCreate some useful content that others may find helpful Leverage Markdown for quick document creation (Previous site was time consuming in my mind to create content, but it did get a start) If possible, figure out how to appropriately handle CI/CD Maybe, maybe, look to moving to a Python static site generator, since I am doing most of my own work in Python. Perhaps GoLang if there is such a thing in that sphere. We will see. The first four posts on this page are pieces that I was able to quickly move over from the previous blogging platform over to the markdown flavor. This explains the timing of this post with having older posts on the blog.\nEvaluation #I originally started with Gitlab, knowing that they had a good exposure of the CI/CD process and had it all integrated. I didn\u0026rsquo;t want to try to integrate a different solution into the Github arena if possible. I tried originally forking the jekyll format over, but this didn\u0026rsquo;t get going well. The CSS never quite made it into the page, so I was frustrated and decided to try Github.\nOver at Github, I could start to get the content, and as soon as I would push a new commit to master I would get an email a few minutes later saying that the build had failed. No other helpful information in the email, just that the Jekyll build had failed. After about four of these messages I decided to try just doing Jekyll on a new VM host of my own and see if I could get it working there.\nDecision Making #I started with a fresh Jekyll page, and immediately things came right up. Finally some progress! Eventually, I found my way to the Jekyll Quickstart/Docs, following that tutorial and looking at other blogging pages on various Github/Gitlab pages I figured out the structure a little bit more.\nI then cloned the repository from Gitlab pages to my local instance and gave it a run. The default page showed immediately. From there I went ahead and copied my posts into the _posts directory and the content was right there. I decided to push the content to my Gitlab pages and low and behold it worked there as well! I was in business.\nSSL and Custom Domain #There are lots of articles all over the web on how to do a custom domain on Gitlab pages. So I\u0026rsquo;m not going to provide details, but high level:\nMake sure your repository name is \u0026lt;userid\u0026gt;.gitlab.io Point your DNS records at Gitlab and away you go SSL was a little more tricky. I had found an \u0026ldquo;official\u0026rdquo; link from Gitlab, but that was not helpful to me. I couldn\u0026rsquo;t get the certificate information to show up with Let\u0026rsquo;s Encrypt. I eventually came across this blog on how to do certificates with Let\u0026rsquo;s Encrypt via Gitlab pages. Once I completed the work done described there, I was able to get my certificates from Let\u0026rsquo;s Encrypt, and I now have a SSL blog.\nFinal Decision: Gitlab Pages #At that point, I finally had a page out on the web and am at the point that I am now. I\u0026rsquo;m going to continue to evolve what the pages will look like. I\u0026rsquo;ve still got some more to learn about how to get Jekyll in the right setup. Maybe a trip to using Python Pelican to build a flavor. But for now, I have a place where I can post material, and so far with writing this post, things are much quicker.\n","date":"2018-11-22","permalink":"https://josh-v.com/getting-started-with-the-blog/","section":"Posts","summary":"\u003cp\u003eWhy this post? Because I decided to change the style of how I was hosting my blog. Before I had decided to just host the blog on something that was easy to get to and update. I could have kept on blogging there, but I found making blog posts a little bit more difficult than what I wanted to. I also wanted to learn some of the \u003ccode\u003enew\u003c/code\u003e ways of doing things within networking technologies.\u003c/p\u003e","title":"Getting Started with the Blog"},{"content":"","date":null,"permalink":"https://josh-v.com/tags/ci/cd/","section":"Tags","summary":"","title":"Ci/Cd"},{"content":"At DevNet Create 2018 there is a video that was put together related to CI/CD in the NetDevOps world. This is something that is very exciting to see that there has been time put in to putting that together. The video can be seen here:\nDevNet Create Video\nPete Lumbis CI/CD Info\nWith tools such as VIRL, EveNG, and GNS3, there should be a methodology to be able to put a network together to do testing. I have not yet put together the full pipeline, but is something that I\u0026rsquo;m very interested in getting built some day.\nOnce a network engineer has a full tested pipeline, we can maybe finally get to doing changes with more confidence and more during normal hours.\nTools that I\u0026rsquo;m aware of for doing the CI/CD pipeline include (definitely not limited to) are: # Jenkins Travis Drone Gitlab Bamboo Some good resources that are available for building Cisco Vagrant VMs #https://github.com/hpreston/vagrant_net_prog/tree/master/box_building#cisco-nexus-9000v\nhttps://techbloc.net/archives/1925\nhttps://techbloc.net/archives/1865\n","date":"2018-11-11","permalink":"https://josh-v.com/network-cicd/","section":"Posts","summary":"\u003cp\u003eAt DevNet Create 2018 there is a video that was put together related to CI/CD in the NetDevOps world. This is something that is very exciting to see that there has been time put in to putting that together. The video can be seen here:\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.youtube.com/watch?v=LinGy8DGIJ8\u0026amp;index=19\u0026amp;list=WL\u0026amp;t=16s\" target=\"_blank\" rel=\"noreferrer\"\u003eDevNet Create Video\u003c/a\u003e\u003cbr\u003e\n\u003ca href=\"https://gitlab.com/plumbis/cumulus-ci-cd\" target=\"_blank\" rel=\"noreferrer\"\u003ePete Lumbis CI/CD Info\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eWith tools such as VIRL, EveNG, and GNS3, there should be a methodology to be able to put a network together to do testing. I have not yet put together the full pipeline, but is something that I\u0026rsquo;m very interested in getting built some day.\u003c/p\u003e","title":"Network CI/CD - work in progress (Links to other videos/pages)"},{"content":"One of the more interesting features that I have just come across within the Ansible automation world is that of the block. I find this very helpful for both error handling, and also grouping tasks into logical separation.\nAnsible Official Link #Ansible Docs: Block\nBlocks allow for logical grouping of tasks and in play error handling. Most of what you can apply to a single task can be applied at the block level, which also makes it much easier to set data or directives common to the tasks. This does not mean the directive affects the block itself, but is inherited by the tasks enclosed by a block. i.e. a when will be applied to the tasks, not the block itself.\nSo what is this about? #The primary reason to use blocks within Ansible is for error handling. I liken this a lot to the Python try: and except: exception handling. You are able to group tasks into one error \u0026ldquo;group\u0026rdquo; and then provides for rescue blocks and always executes blocks. This can be extremely helpful.\nUsing with when #I\u0026rsquo;ve found that a second place to put in blocks within Ansible is to also pair it with a when: statement to help separate out tasks. Some may put this into a different play, but the down side of this is when you are leveraging variables. With separate plays you will be defining variables within each play. With using blocks to define what to do when, can be very helpful.\n","date":"2018-06-07","permalink":"https://josh-v.com/ansible-blocks/","section":"Posts","summary":"\u003cp\u003eOne of the more interesting features that I have just come across within the Ansible automation\nworld is that of the \u003ccode\u003eblock\u003c/code\u003e. I find this very helpful for both error handling, and also grouping\ntasks into logical separation.\u003c/p\u003e\n\u003ch2 id=\"ansible-official-link\" class=\"relative group\"\u003eAnsible Official Link \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#ansible-official-link\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h2\u003e\u003cp\u003e\u003ca href=\"https://docs.ansible.com/ansible/latest/user_guide/playbooks_blocks.html\" target=\"_blank\" rel=\"noreferrer\"\u003eAnsible Docs: Block\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eBlocks allow for logical grouping of tasks and in play error handling. Most of what you can apply\nto a single task can be applied at the block level, which also makes it much easier to set data\nor directives common to the tasks. This does not mean the directive affects the block itself, but\nis inherited by the tasks enclosed by a block. i.e. a when will be applied to the tasks, not the\nblock itself.\u003c/p\u003e","title":"Ansible Blocks"},{"content":"Get notified when I publish new posts on network automation, AI, Python, and more.\nNo spam. Just new content when it\u0026rsquo;s ready. Unsubscribe any time.\nSubscribe No spam. Unsubscribe any time.\n","date":null,"permalink":"https://josh-v.com/subscribe/","section":"Subscribe","summary":"\u003cp\u003eGet notified when I publish new posts on network automation, AI, Python, and more.\u003c/p\u003e\n\u003cp\u003eNo spam. Just new content when it\u0026rsquo;s ready. Unsubscribe any time.\u003c/p\u003e\n\n\n\n\u003cform\n  id=\"page-subscribe-form\"\n  class=\"not-prose flex flex-col gap-3 sm:flex-row sm:items-center\"\n\u003e\n  \u003cinput\n    type=\"email\"\n    name=\"email\"\n    placeholder=\"you@example.com\"\n    required\n    class=\"flex-1 rounded-md border border-neutral-300 bg-neutral px-3 py-2 text-sm text-neutral-900 placeholder-neutral-400 focus:border-primary-500 focus:outline-none dark:border-neutral-600 dark:bg-neutral-800 dark:text-neutral dark:placeholder-neutral-500\"\n  /\u003e\n  \u003cbutton\n    type=\"submit\"\n    class=\"rounded-md bg-primary-600 px-5 py-2 text-sm font-semibold text-white hover:bg-primary-700 focus:outline-none\"\n  \u003e\n    Subscribe\n  \u003c/button\u003e\n\u003c/form\u003e\n\u003cp id=\"page-subscribe-msg\" class=\"mt-2 text-xs text-neutral-500 dark:text-neutral-400\"\u003eNo spam. Unsubscribe any time.\u003c/p\u003e\n\u003cscript\u003e\n(function() {\n  var form = document.getElementById('page-subscribe-form');\n  var msg = document.getElementById('page-subscribe-msg');\n  if (!form) return;\n  form.addEventListener('submit', function(e) {\n    e.preventDefault();\n    var email = form.querySelector('input[name=\"email\"]').value;\n    var btn = form.querySelector('button');\n    btn.disabled = true;\n    btn.textContent = 'Subscribing...';\n    fetch('https:\\/\\/blog-newsletter.bitter-frost-f4e5.workers.dev\\/subscribe', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({ email: email })\n    })\n    .then(function(r) { return r.json(); })\n    .then(function(data) {\n      if (data.error) {\n        msg.textContent = data.error;\n        msg.style.color = '#ef4444';\n      } else {\n        msg.textContent = data.message || 'Check your email to confirm!';\n        msg.style.color = '#22c55e';\n        form.reset();\n      }\n      btn.disabled = false;\n      btn.textContent = 'Subscribe';\n    })\n    .catch(function() {\n      msg.textContent = 'Something went wrong. Please try again.';\n      msg.style.color = '#ef4444';\n      btn.disabled = false;\n      btn.textContent = 'Subscribe';\n    });\n  });\n})();\n\u003c/script\u003e","title":"Subscribe"}]