Docs as Code, Diagrams as Code
Everything-as-Code has been a very popular topic over the last 5 years or so. Mostly, as IT continues to move faster and automation is needed to meet business timelines and guaranteed quality. Recently, I have been helping a customer set standards for engineering documentation. One of the standards WWT uses is MkDocs for Documentation-as-Code.
Acknowledgements
I would like to start by thanking Tim Hull, who is the WWT SME for Docs as Code. Tim has helped me, and numerous customers, understand how to build documentation into their CI/CD pipeline.
What is needed
For this Proof of Concept I decided to automate the documentation of a vSphere environment. PowerCLI is the easiest and most powerful tool for querying vSphere environments, so I used that tool. For your environment, another tool may be easier.
- Python
- Python-PIP
- PowerShell (Windows) or PowerShell Core (Linux, MacOS)
- PowerCLI
- MkDocs
How to build and automate
Install all the software components
- Install python and python-pip for your OS using the links above. I'm using Linux, so the install was:
sudo apt install python
sudo apt install python-pip
- Install Powershell for your OS using the link above. I'm using Linux, so the install was:
curl https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --yes --dearmor --output /usr/share/keyrings/microsoft.gpg
sudo sh -c 'echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/microsoft-debian-bullseye-prod bullseye main" > /etc/apt/sources.list.d/microsoft.list'
sudo apt update && sudo apt install -y powershell
- Install PowerCLI in PowerShell:
pwsh
> Install-Module VMware.PowerCLI -Scope CurrentUser
- Install mkdocs (and the optional mkdocs material theme):
pip install mkdocs
$ pip install mkdocs-material
Automate your documentation
Start a new MkDocs project
mkdir mkdocs-project
cd mkdocs-project
mkdocs new .
INFO - Writing config file: ./mkdocs.yml
INFO - Writing initial docs: ./docs/index.md
Change the theme to material (Optional)
vi mkdocs.yml
site_name: Dennis' Docs
theme:
name: material
features:
- navigation.tabs
- navigation.sections
- toc.integrate
- navigation.top
- search.suggest
- search.highlight
- content.tabs.link
- content.code.annotation
- content.code.copy
language: en
palette:
- scheme: default
toggle:
icon: material/toggle-switch-off-outline
name: Switch to dark mode
primary: teal
accent: purple
- scheme: slate
toggle:
icon: material/toggle-switch
name: Switch to light mode
primary: teal
accent: lime
copyright: |
© 2023 <a href="https://github.com/dennisfaucher" target="_blank" rel="noopener">Dennis Faucher</a>
Fire up the development MkDocs web server
This web server will show you your documentation changes live as they happen.
mkdocs serve -a 0.0.0.0:8000
WINFO - Building documentation...
INFO - Cleaning site directory
INFO - Documentation built in 0.18 seconds
INFO - [08:22:48] Watching paths for changes: 'docs', 'mkdocs.yml'
INFO - [08:22:48] Serving on http://0.0.0.0:8000/
Write your automation script
You can use any API language that works for you. The vCenter PowerShell API works very well, so I used that. Here is the script I wrote that authenticates to vCenter, writes the index.md markdown file based on PowerCLI commands, builds the web site and uses scp to copy the site to my web server.
If you need help with password-less ssh/scp, take a look at this post.
Note: I learned that the escape character for Powershell is "`". Also, so I did not have to show my vCenter password in the script, I created a file in docs named clear_password.txt and put my vCenter password in there.
cd docs
vi pwsh_build_vcenter_mkdocs.ps1
# Create the header
echo "# vCenter Automagic MkDocs Documentation" > index.md
echo "" >> index.md
echo "For full documentation visit [mkdocs.org](https://www.mkdocs.org)." >> index.md
# Login to vCenter
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false
$Password = Get-Content "/home/dennis/Documents/MkDocs/vcenter-v2/docs/clear_password.txt"
Connect-viserver -server 192.168.1.55 -user administrator@vsphere.local -password $Password
# Build the host information
echo "## 🖥 Host Information 🖥" >> index.md
echo "````````bash" >> index.md
Get-VMHost | select Name,NumCpu,ProcessorType,CpuUsageMhz,MemoryUsageGB,Version | Format-Table >> /home/dennis/Documents/MkDocs/vcenter-v2/docs/index.md
echo "````````" >> index.md
# Build the VM information
echo "## 🫙 VM Information 🫙" >> index.md
echo "````````bash" >> index.md
Get-VM |select Name,NumCpu,MemoryGB,PowerState | Format-Table >> /home/dennis/Documents/MkDocs/vcenter-v2/docs/index.md
echo "````````" >> index.md
# Build the Datastore information
echo "## 💾 Datastore Information 💾" >> index.md
echo "````````bash" >> index.md
Get-datastore | Format-Table >> /home/dennis/Documents/MkDocs/vcenter-v2/docs/index.md
echo "````````" >> index.md
# Build the vSwitch information
echo "## 🔌 vSwitch 🔌 " >> index.md
echo "````````bash" >> index.md
Get-virtualswitch | Format-Table >> /home/dennis/Documents/MkDocs/vcenter-v2/docs/index.md
echo "````````" >> index.md
# Build the documentation web site
cd /home/dennis/Documents/MkDocs/vcenter-v2
mkdocs build
# Copy the site to the web server
scp -r /home/dennis/Documents/MkDocs/vcenter-v2/site/* dennis@plex-addons:/var/www/html/mkdocs/
That's it. "pwsh -F pwsh_build_vcenter_mkdocs.ps1" will run this script. Any time the script is run, the documentation gets updated. Nice. You can find my live documentation web site here.
Extra credit: Automate diagrams with 🧜♀️mermaid🧜♀️
Beside automating beautiful text-based diagrams with regular markdown, you can also automate beautiful diagrams with mermaid markdown. Mermaid allows you to produce many types of diagrams in markdown: Flowchart, Sequence Diagram, Class Diagram, State Diagram, Entity Relationship Diagram, User Journey, Gantt, Pie Chart, Quadrant Chart, Requirement Diagram, Gitgraph (Git) Diagram, C4 Diagram, Mindmaps, Timeline, Zenuml and Sankey.
Add the Mermaid module to MkDocs
In order for mkdocs to understand mermaid syntax, you need to include the mermaid module in your mkdocs.yml file which is in the root of your mkdocs project directory. Here is the root directory of a standard mkdocs project:
ls -a
. .. .cache docs mkdocs.yml site
In my markdown_extensions section of mkdocs.yml, I needed to add these lines:
markdown_extensions:
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
Add the Mermaid instructions to the PowerShell script
Mermaid syntax isn't very hard and there are good examples on the mermaid website. I just needed to get my head around how to pull the vSphere data I needed with PowerCLI and convert that to mermaid notation.
Open the mermaid markdown section
#Open up Mermaid
echo "``````mermaid" >> index.md
Mermaid opens with ```mermaid but I needed to escape every backtick with a PowerShell backtick.
Define a flowchart diagram
echo "flowchart TD;" >> index.md
🖥Define the vSphere hosts as square boxes🖥
# Build the host information
$varVMHosts = Get-VMHost | select Name
foreach ($varHostName in $varVMHosts)
{
$varHostMermaid = $varHostName.name + "[" + $varHostName.name + "];"
Write-Output $varHostMermaid >> index.md
}
In mermaid, square boxes are defined by their variable name plus text inside square brackets. To make the coding easier, I used the hostname as both the variable and the diagram text. This hosts end up looking like this:
🫙Define the vSphere VMs as circles🫙
# Build the VM information
$varVMs = (Get-VM).where{$_.PowerState -eq 'PoweredOn'} | select VMHost,Name,DatastoreIdList
foreach ($varVMName in $varVMs)
{
$varVMMermaid = $varVMName.Name + "((" + $varVMName.Name + "));"
Write-Output $varVMMermaid >> index.md
}
In order to keep my diagram easier to read, I filtered the PowerCLI query to only return powered on VMs with the "where" statement you see above.
In mermaid, circles are defined by their variable name plus text inside double parentheses. The VMs end up looking like this:
💾Define the vSphere datastores as cylinders💾
# Build the Datastore information
$varDatastores = Get-DataStore | select Name, ID
foreach ($varDSName in $varDatastores)
{
$varVMMermaid = $varDSName.ID + "[(" + $varDSName.Name + ")];"
Write-Output $varVMMermaid >> index.md
}
In mermaid, cylinders are defined by their variable name plus text inside square bracket plus parentheses. I use the datastore ID as the variable name and the datastore name as the text. The datastores end up looking like this:
Now that we have all the variables and shapes defined, we can describe how they all connect to one another. So far, our simple vSphere server mermaid index.md markdown looks something like this:
```mermaid
flowchart TD;
nuc2[nuc2];
Graylog((Graylog));
VCSA((VCSA));
Plex-Add-Ons((Plex-Add-Ons));
Plex((Plex));
SyncThing((SyncThing));
kubuntu((kubuntu));
Jellyfin((Jellyfin));
Grafana((Grafana));
open-semantic((open-semantic));
Datastore-datastore-12004[(datastore1)];
Connect the hosts to the VMs
Connecting any shape to any shape is as simple as adding "-->" between the shape variable names. Other styles of line connectors are defined in the flowchart section of the mermaid site. Here is the code:
foreach ($varVMName in $varVMs)
{
$varVMMermaid = $varVMName.VMHost.Name + " --> " + $varVMName.Name + ";"
Write-Output $varVMMermaid >> index.md
}
Connect the VMs to the datastores
*POC Note: In my simple vSphere server POC, I only have one datastore and my code does not include logic to connect one VM to more than one datastore. PowerCLI returns a list of datastores for each VM. more elegant code would walk through this list of datastores. Here is the "inelegant" code:
foreach ($varVMName in $varVMs)
{
$varVMMermaid = $varVMName.Name + " --> " + $varVMName.DatastoreIdList + ";"
Write-Output $varVMMermaid >> index.md
}
Close the Mermaid markdown section
#Close Out Mermaid
echo "``````" >> index.md
That's it. The entire mermaid markdown will look something like this:
```mermaid
flowchart TD;
nuc2[nuc2];
Graylog((Graylog));
VCSA((VCSA));
Plex-Add-Ons((Plex-Add-Ons));
Plex((Plex));
SyncThing((SyncThing));
kubuntu((kubuntu));
Jellyfin((Jellyfin));
Grafana((Grafana));
open-semantic((open-semantic));
Datastore-datastore-12004[(datastore1)];
nuc2 --> Graylog;
nuc2 --> VCSA;
nuc2 --> Plex-Add-Ons;
nuc2 --> Plex;
nuc2 --> SyncThing;
nuc2 --> kubuntu;
nuc2 --> Jellyfin;
nuc2 --> Grafana;
nuc2 --> open-semantic;
Graylog --> Datastore-datastore-12004;
VCSA --> Datastore-datastore-12004;
Plex-Add-Ons --> Datastore-datastore-12004;
Plex --> Datastore-datastore-12004;
SyncThing --> Datastore-datastore-12004;
kubuntu --> Datastore-datastore-12004;
Jellyfin --> Datastore-datastore-12004;
Grafana --> Datastore-datastore-12004;
open-semantic --> Datastore-datastore-12004;
```
Thank you
Thanks for taking the time to read this post. I hope you found the information helpful, and maybe it saved you some time. We love feedback, so please add comments if you have any.