Dynamicly executing powershell code from GIT – a FaaS way

In this post I mentioned that I was tipping my toes in vRA.
And…. yeah, it is not dipping anymore, it is a deep dive… 🙂 but that is for another blog

This blog is about a challenge I solved by using a FaaS approach. FaaS stands for Function as a Service.
I have a small UPS running for my home environment. And found out that it could support my home environment for about 30 min. And then poof… power off…. So I was searching for a solution that my vsphere environment would be shutdown, triggered from a home assistant /Node-Red automation flow, monitoring my UPS status.

My first way of thinking was to use the REST API of vSphere / VMware esxi. But not all the actions I need are published. (Like shutdown of an ESXi host.)
And I want to be dynamic as possible.
I want to shutdown / suspend all VMs expect VMs that are tagged as coreVM. So the code that I should write, doesn’t contain the VM names or IDs, but would filter out all VMs that have a vSphere tag with coreVM.

The Idea

As mentioned, is that the automation would find all VMs that need to be suspended or shutdown ( depending if VMtools is running). And VMs that have a vSphere tag UPS/coreVM, would be ignored.

These VMs are controlled with the startup/shutdown feature of my ESXi host.
The total automation flow would be a 3 fase flow. Fase 1 shutdown all but the core VMs. Fase 2 shutdown the ESXi host, which would automaticly shutdown VMs controlled via the startup/shutdown feature. Fase 3, shutdown of Synology NAS and Home Assistant
The core VMs are my vCenter, Log Insight and router VM.

The tools

The tools I use are:

  • Home Assistant with
    • NUT intergration (monitoring my UPS)
    • Node-Red flow automation (ussing HTTP REST API calls to control docker via Portainer)
  • Docker CE with Portainer CE running on a synology NAS
    • Docker image vmware/powerCLI
    • Portainer controlling the docker environment and leveraging control via its APIs
  • Gitea GIT server running locally (containing my script and parameter files)

the FaaS way

For fase 2 I try to use a FaaS approach. Meaning I have a function for shutting down / suspending VMs. This function is run in a temporary runtime environment (docker container) and only available at runtime. The function is not part of the runtime environment, but on runtime it is downloaded from a GIT repository and executed.
This gives the advantage of maintaining a runtime environment seperate from scripts (or the other way around). And on every execution, it starts with a clean environment.
For the function I use powerCLI, because I haven’t found an API call in vSphere 7.0 that will shutdown an ESXi host. And filtering VMs on vsphere tags was (for me) a bridge to far.

Using a container gives me also the possibility to seperate my vSphere credentials from my script, by saving it in a docker volume, which is only mounted at runtime. The function itselfs contains no credentials. It looks to a file in that docker volume.

The hurdles I encountered were:

  • how to run the script from a git repo
    • by using a powershell one-line invoke-expression with invoke-webrequest using the url to the raw presentation of a file (script) in the git repo.
    • using this one-liner as the entrypoint parameter when creating the container
  • how to bypass caching by the web browser
    • using “Cache-Control”=”no-store” in the header
  • running a full powershell script via invoke-expression
    • write the full code block as a powershell function (with begin, End and Process blocks) and call that function within the script.

The powershell one-line is

pwsh -Command invoke-expression '$(Invoke-WebRequest -SkipCertificateCheck -uri ' + <git URI> + ' -Headers @{"Cache-Control"="no-store"} )'

The <git URI> is the url to the raw representation of the script file in the GIT repo. When leveraging the portainer/docker api to create the container you need to use the JSON notation for the entrypoint. The one-line will look something like:

["pwsh",
"-Command",
"invoke-expression",
'$(Invoke-WebRequest -SkipCertificateCheck -uri ' + <git URI> + ' -Headers @{"Cache-Control"="no-store"} )']

Powershell script formant / the snag

The snag with using invoke-expression is that it can’t handle a powershell script that has a Begin, End and Process code block. While I this is how I write my code, and wouldn’t like to deviate from it, it meant I had a snag.
The solution was to write a powershel script that contains a private function and execution of that function, like this

function main {
    <#
    .SYNOPSIS
        Example code for running full script from an URI.
    .DESCRIPTION
        Example code to run a powershell script with dynamic blocks Begin,Process and End.
        Loading parameters from a JSON file into an P object.
        By wrapping the code into the function main, we can use begin, process and end scriptblocks when calling with Invoke-Expression
        The Process block contains the main code to execute.
        The Begin and en blocks are mainly used for setting up the environment and closing it.
    .EXAMPLE
    Run the following cmdline in a powershell session.
    Invoke-Expression (Invoke-webrequest <URL>).content
    .NOTES    
    #>
    Begin {
        #=== Script parameters

        #-- GIT repository parameters for loading the parameters.json
        $scriptName="startVMs"
        $scriptGitServer = "https://....."   # IPv4 or FQDN of GIT server
        $scriptGitRepository = "organisation/repo/" # uri part containing git repository
        $scriptBranch = "master/" # GIT branch
        $scriptrootURI = $scriptGitServer+$scriptGitRepository+"raw/branch/"+$scriptBranch

        #==== No editing beyond this point !! ====
        $ts_start=get-date #-- Save current time for performance measurement

        #--- write log header to console
        write-host
        write-host "================================================================="
        write-host ""
        write-host "script: $scriptName.ps1"
        write-host ""
        write-host "-----------------------------------------------------------------"

        #-- trying to load parameters into $P object, preferably json style
        try { $webResult= Invoke-WebRequest -SkipCertificateCheck  -Uri ($scriptrootURI+$scriptName+".json") -Headers @{"Cache-Control"="no-store"}  }
        catch  {
            write-host "uri : " + $scriptrootURI
            throw "Request failed for loading parameters.json with uri: " + $webResult 
        }
        # validate answer
        if ($webResult.StatusCode -match "^2\d{2}" ) {
            # statuscode is 2.. so convert content into object $P
            $P = $webResult.content | ConvertFrom-Json 
        } else {
            throw ("Failed to load parameter.json from repository. Got statuscode "+ $webRequest.statusCode)
        }
    }
    End {

    }
    Process {

    }
}
main

What it does, is the script will run the function main. That function is basicly the full powershell script. In the begin code block the invoke-Webrequest is used to load a .json file and convert it to a powershell object called P.
This object contains all parameters used in the rest of script (like vCenter FQDN).

Result

The result is that from a monitoring trigger Node-Red will do some REST API calls to portainer to create run and delete a docker container based on a vmware/powerCLI image. During the lifetime of this container a volume is mounted with authorisation information and a powershell one-liner is executed which runs a powershell code directly loaded from a GIT repository.
With this setup I can run on demand any powershell script which doesn’t need user interaction, maintained in a GIT repository.

I hope you enjoyed this post. do you have questions / comments, please leave them below or reach out to me on twitter

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: