{"id":2148,"date":"2025-01-30T21:45:53","date_gmt":"2025-01-31T02:45:53","guid":{"rendered":"https:\/\/enterpriseadmins.org\/blog\/?p=2148"},"modified":"2025-01-30T21:45:53","modified_gmt":"2025-01-31T02:45:53","slug":"extending-aria-automation-with-custom-resources-and-actions-for-ip-address-management","status":"publish","type":"post","link":"https:\/\/enterpriseadmins.org\/blog\/virtualization\/extending-aria-automation-with-custom-resources-and-actions-for-ip-address-management\/","title":{"rendered":"Extending Aria Automation with Custom Resources and Actions for IP Address Management"},"content":{"rendered":"\n<p>In my lab, I leverage Aria Automation to deploy Linux, Windows, and nested ESXi VMs.  This is my primary interface for requesting new systems and covers most of the common resources I need for testing.  However, I sometimes deploy one off appliances and such, at a scale where automation hasn&#8217;t been built.  These appliances typically require an IP Address and DNS record.  I had previously created a Jenkins job that accepted parameters, making these easy enough to create, but the cleanup is where I would fall down. I also wasn&#8217;t a huge fan of switching between the Aria Automation &amp; Jenkins consoles to submit these requests.<\/p>\n\n\n\n<p>My ideal solution to both of these problems was an Aria Automation request form that would create a deployment tracking these one-off IP requests.  To not re-invent the wheel, this Aria Automation request could simply call Jenkins.  When testing is complete, I&#8217;d have a deployment remaining in Aria Automation to serve as a reminder to properly clean up IPAM and DNS.  This article will cover the process of creating this action, resource, and template to front end the Jenkins request with Aria Automation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Custom Action &#8211; Create<\/h2>\n\n\n\n<p>In Aria Automation Assembler > Extensibility > Actions, we can create a new action. I named mine <code>IPAM Next Address Create<\/code> and selected only the project where my test deployments live. <\/p>\n\n\n\n<p>For the action, I&#8217;m writing everything in PowerShell, since I already know that language and Aria Automation supports it.  This code sample lacks robust error handling, and probably could be cleaned up a fair amount, but it got the job done for what I was hoping for.  In a production environment, adding some logic after each step to ensure the task completed would be prudent.  In the event that the IPAM service is down or Jenkins isn&#8217;t responding, we&#8217;d want the request to behave in a predictable way. <\/p>\n\n\n\n<p>The create section has more code as it connects to phpIPAM to get the next address then requests a DNS record be created by Jenkins.  I directly obtain the IP address, so that I have it to return as part of the deployment, so that the IP obtained is clearly visible in the deployment.  <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function handler($context, $inputs) {\n    $subnet = $inputs.subnet\n    $hostname = $inputs.name\n    \n    write-host \"We've received a $($inputs.'__metadata'.operation) request for subnet $subnet\"\n \n    $ipamServer = 'ipam.apps.example.com'\n    $ipamUser   = 'svc-vra'\n    $ipamPass   = 'VMware1!'\n    $ipamBaseURL = 'https:\/\/'+$ipamServer+'\/api\/'+$ipamUser+'\/'\n\n    # Login to the API with username\/password provided.  Create header to be used in next requests.\n    write-host \"IPAM Login\"\n    $ipamLogin = (Invoke-RestMethod -Uri \"$($ipamBaseURL)user\" -Method Post -SkipCertificateCheck -Headers @{'Authorization'='Basic '+&#91;Convert]::ToBase64String(&#91;Text.Encoding]::ASCII.GetBytes($ipamUser+':'+$ipamPass))}).data.token\n    $nextHeader = @{'phpipam-token'=$ipamLogin}\n\n    # Get the subnet ID of the specified CIDR\n    write-host \"IPAM Get Subnet ID\"\n    $subnetID = (Invoke-RestMethod -URI \"$($ipamBaseURL)subnets\/cidr\/$subnet\" -SkipCertificateCheck -Headers $nextHeader).data.id\n\n    # Make a reservation and provide name\/description\n    write-host \"IPAM Reserve Next\"\n    $postBody = @{hostname=\"$($hostname).lab.enterpriseadmins.org\"; description='Requested via Automation Extensibility'}\n    $myIPrequest = (Invoke-RestMethod -URI \"$($ipamBaseURL)addresses\/first_free\/$subnetID\" -SkipCertificateCheck -Method Post -Headers $nextHeader -Body $postBody).data\n    \n    # Send a DNS Request to Jenkins\n    write-host \"Jenkins DNS Request\"\n    $dnsBody = @{reqtype='add'; reqhostname=$hostname; reqipaddress = $myIPrequest; reqzonename='lab.enterpriseadmins.org'} | ConvertTo-Json\n    Invoke-RestMethod -URI 'http:\/\/jenkins.example.com:8080\/generic-webhook-trigger\/invoke?token=VRA-dnsRecord' -Method Post -Body $dnsBody -ContentType 'application\/json'\n\n    # Return detail to vRA\n    $outputs = @{\n        address = $myIPrequest\n        resourceName = $hostname\n    }\n    return $outputs\n}<\/code><\/pre>\n\n\n\n<p>The IP address obtained from IPAM as well as the hostname are returned when this task completes.  <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Custom Action &#8211; Read<\/h2>\n\n\n\n<p>For our custom resource, we will also need to specify an action to read \/ check status of our resource.  For my purposes, I really don&#8217;t need anything specific to be checked, so I simply return all the input parameters.  This is the default function \/ template loaded when creating the action.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function handler($context, $inputs) {\n    return $inputs\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Custom Action &#8211; Delete<\/h2>\n\n\n\n<p>When we are finished with our deployment and ready to delete, the custom resource needs a &#8216;delete&#8217; action to call.  Again this is written in PowerShell and calls Jenkins to request the actual delete.  Jenkins will then connect to DNS and IPAM to process the cleanup.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function handler($context, $inputs) {\n    $ipAddress = $inputs.address\n    $hostname = $inputs.name\n    \n    write-host \"We've received a $($inputs.'__metadata'.operation) request for IP address $ipAddress and hostname $hostname\"\n     \n    $removeBody = @{reqzonename='lab.enterpriseadmins.org'; operationType='remove'; reqhostname=$hostname; subnetOrIp = $ipAddress} | ConvertTo-Json\n    Invoke-RestMethod -URI 'http:\/\/jenkins.example.com:8080\/generic-webhook-trigger\/invoke?token=RequestIpAndDnsRecord' -Method Post -Body $removeBody -ContentType 'application\/json'\n}<\/code><\/pre>\n\n\n\n<p>This code could easily have contacted IPAM and DNS as separate requests, but since the Jenkins job already existed with webhook support, I choose to follow that path for simplicity.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Create Custom Resource<\/h2>\n\n\n\n<p>In Aria Automation Assembler > Design > Custom Resources we can create a new resource which will run our above actions. I named my resource <code>IPAM Next Address<\/code>, set the resource type to <code>Custom.IPAM.Request<\/code>, and based the resource on an ABX user-defined schema. For lifecycle actions, I selected the above IPAM Next Address action for all three required types: create, read, and destroy. For starters I set the scope to only be available for my test project, and finally togged the &#8216;activate&#8217; switch to make the resource available in blueprints.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2025\/01\/image-10.png\"><img loading=\"lazy\" decoding=\"async\" width=\"850\" height=\"577\" src=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2025\/01\/image-10.png\" alt=\"\" class=\"wp-image-2194\" srcset=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2025\/01\/image-10.png 850w, https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2025\/01\/image-10-300x204.png 300w, https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2025\/01\/image-10-768x521.png 768w\" sizes=\"auto, (max-width: 850px) 100vw, 850px\" \/><\/a><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2025\/01\/image-9.png\"><img loading=\"lazy\" decoding=\"async\" width=\"828\" height=\"714\" src=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2025\/01\/image-9.png\" alt=\"\" class=\"wp-image-2193\" srcset=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2025\/01\/image-9.png 828w, https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2025\/01\/image-9-300x259.png 300w, https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2025\/01\/image-9-768x662.png 768w\" sizes=\"auto, (max-width: 828px) 100vw, 828px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Create Template<\/h2>\n\n\n\n<p>In Aria Automation Assembler > Design > Custom Template, the design for this request is super simple. There are three inputs: <code>issueNumber<\/code>, <code>Name<\/code>, and <code>Subnet<\/code>. The issue number is used for tracking and becomes part of the host name. The name is the unique part of the hostname, and the subnet is which network to use when finding the next address.  My hostname ends up being <code>h&lt;issue-number-padded-3-digits>-&lt;name-entered><\/code> (h is a prefix I use for test systems in my homelab).   The subnet is a drop-down list with the networks I typically use for testing, defaulting to the selection I use most often.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>formatVersion: 1\ninputs:\n  issueNumber:\n    type: integer\n    title: Issue Number\n  Name:\n    type: string\n    minLength: 1\n    maxLength: 25\n    default: ip-01\n  Subnet:\n    type: string\n    title: Subnet\n    default: 192.168.10.0\/24\n    enum:\n      - 192.168.10.0\/24\n      - 192.168.40.0\/24\nresources:\n  IPAddress:\n    type: Custom.IPAM.Request\n    properties:\n      name: h${format(\"%03d\",input.issueNumber)}-${input.Name}\n      subnet: ${input.Subnet}\n      address: ''\n      git-issue-number: ${input.issueNumber}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Deploy<\/h2>\n\n\n\n<p>Once I published a version of this design, I can now make a request from the service broker catalog.  My request form only has a few required fields:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2024\/12\/image-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"557\" height=\"356\" src=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2024\/12\/image-1.png\" alt=\"\" class=\"wp-image-2152\" srcset=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2024\/12\/image-1.png 557w, https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2024\/12\/image-1-300x192.png 300w\" sizes=\"auto, (max-width: 557px) 100vw, 557px\" \/><\/a><\/figure>\n\n\n\n<p>I added some functionality into the &#8216;create&#8217; action to post a comment to my issue tracker letting me know that a new resource has been created. It is created with a task list check box, so that I can see there is an open item to review with this issue, as well as a link to the deployment.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2024\/12\/image-2.png\"><img loading=\"lazy\" decoding=\"async\" width=\"500\" height=\"108\" src=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2024\/12\/image-2.png\" alt=\"\" class=\"wp-image-2153\" srcset=\"https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2024\/12\/image-2.png 500w, https:\/\/enterpriseadmins.org\/blog\/wp-content\/uploads\/2024\/12\/image-2-300x65.png 300w\" sizes=\"auto, (max-width: 500px) 100vw, 500px\" \/><\/a><\/figure>\n\n\n\n<p>When I look at the deployment, I can see when it was created, if it expires, and can use the actions drop down to delete the deployment. This delete action calls the Jenkins job mentioned above to remove the DNS record and release the IP address from IPAM.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Aria Automation can provide an interface to leverage existing workflows.  This example shows how to create a deployment to track the lifecycle of a created resource, while leveraging an existing system to handle the actual task.  This solves my cleanup \/ tracking issue for one off IP requests as well as getting all the requests submitted from a single console.  Hopefully you can use pieces of this workflow in your own environment.  <\/p>\n","protected":false},"excerpt":{"rendered":"<p>In my lab, I leverage Aria Automation to deploy Linux, Windows, and nested ESXi VMs. This is my primary interface for requesting new systems and covers most of the common resources I need for testing. However, I sometimes deploy one &hellip; <a href=\"https:\/\/enterpriseadmins.org\/blog\/virtualization\/extending-aria-automation-with-custom-resources-and-actions-for-ip-address-management\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":6,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[9,4],"tags":[],"class_list":["post-2148","post","type-post","status-publish","format-standard","hentry","category-lab-infrastructure","category-virtualization"],"_links":{"self":[{"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/posts\/2148","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/comments?post=2148"}],"version-history":[{"count":4,"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/posts\/2148\/revisions"}],"predecessor-version":[{"id":2195,"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/posts\/2148\/revisions\/2195"}],"wp:attachment":[{"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/media?parent=2148"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/categories?post=2148"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/enterpriseadmins.org\/blog\/wp-json\/wp\/v2\/tags?post=2148"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}