Recently, I listened to the PowerShell Podcast episode with Barbara Forbes and she mentioned devcontainers and her blog post: Use GitHub Codespaces for Azure PowerShell Function apps
I've heard so much dev containers, like Brett Miller talks about them all the time, too.
In addition to Brett, other PowerShell friends, including Jess Pomfret and Rob Sewell and Shawn Melton are suggesting devcontainers, so the moment I was back at my computer, I read Barbara's blog post, cloned the repo and messed around.
This repository is the result.
There are a few key differences between my repo and Barbara's; primarily I updated the devcontainer version to PowerShell v7 and the Azure Functions to v4. I also added a sample Azure Function and removed a few default OS-related errors/warnings.
Here's a gem: according to DevContainers for Azure and .NET, here's the build order for devcontaineres.
- Build the Docker container. If you add the shell script through the RUN command in Dockerfile, the shell script is run this time.
- Run features declared in the features section of devcontainer.json while building the Docker container.
- Run commands declared in the postCreateCommand attribute of devcontainer.json.
- Apply dotfiles after postCreateCommand, if you have it.
- Apply both extensions and settings of devcontainer.json at the startup of the DevContainer.
So here's directory structure for what I think I'll be using a template for my Azure Function repos.
.
├── .devcontainer
│ ├── Dockerfile
│ └── devcontainer.json
├── .github
│ └── workflows
│ └── function.yml
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── README.md
└── functions
├── .gitignore
├── Modules
│ └── azHelper
├── azHelper.psd1
└── azHelper.psm1
├── host.json
├── local.settings.json
├── profile.ps1
├── requirements.psd1
├── templates
└── greeting
├── function.json
└── run.ps1
This directory contains a Dockerfile
that builds the container. I could also add in a docker-compose.yml
file which I probably will later with more advanced setups when I want to include a "network" of containers, like for SQL Server projects.
I added more VS Code customizations into the devcontainer.json
to address warnings that pop up about Windows PowerShell not existing, so I forced the dev container to look for PowerShell on Linux.
I also removed the following line:
"ghcr.io/devcontainers/features/azure-cli:1": {}
I saw GitHub's Dev Container Features used by others, including Jess who uses it to install both the azurecli and git. I think features are literally compiled to ensure they work well (maybe?) because it took a long time for the features to install on my container.
Just figured it out! I was installing git and the feature literally says "(from source)" so it was indeed compiling git.
Ultimately, the updated Azure Functions container already has the Azure CLI installed and I just used apt get
to install whatever else I wanted in the Dockerfile
. This seemed much faster.
I'll probably revist this decision in the future, though, because the list of features seems super useful.
Azure Functions can be published automatically from GitHub Actions! I haven't done it yet but this folder contains is the workflow template that was provided by Microsoft.
That Azurite Azure Storage Emulator creates a ton of files named like __azurite_db_blob_extent__.json
so I ignore them all in git and VS Code.
The VS Code files are pretty standard but I did add a Node entry to launch.json
because it worked well for me in the past (and it ran PowerShell Azure Functions) and I like options. So far, though, the PowerShell entry is my default.
This has an important setting: the root location of my Azure Functions, which I simply named functions
.
"azureFunctions.deploySubpath": "functions",
I also reiterated the location of PowerShell on Linux. Without this one (or maybe the other entry in devcontainer.json
), I get the following error when attempting to debug:
If you see that, btw, try just restarting your VS Code and that often times can help.
Oh, and I ignored all those files that Azurite generates.
Took me a while but this is what I decided to name the folder that contains the actual functions. There doesn't seem to be a standard in Azure Function repos and I like this one.
This folder is important in PowerShell projects! The path is essenetially added to the system's $env:PSModulePath.
Rob likes to place all module dependencies here, saying..
the reason is to speed up function load time - because if we are on pay per minute we are paying to install the module every time so I was going to do a build of the repo every night and inject the latest dbatools release. Ultimately, putting the modules inside the container saves money and pins the version at the cost of requiring effort to maintain and update. Using requirements risks upstream dependencies breaking your shit but is much easier and simpler to add and remove new ones.
He's got a point there, especially considering how often the PowerShell Gallery fails :/ He went on to say
It depends on each projects specifical requirements. But i think using requirements up front means that people don't need to know or worry about dealing with containers. So it's easier and when you have problems you need to come back and revisit
Barbara prefers requirements.psd1
, saying..
I would use requirements.psd1 unless there is a good reason not to. Good reason being that the modules are not in the Gallery, for example. Getting them from the Gallery means you have less management and always have the latest (major) version. Another reason can be very large modules. If you load the complete Az module, it takes more than 5 minutes and that ruins your start-time. So with Az you can use submodules, which will resolve that issue.
I'm still undecided (considering the Gallery's instability) but I think I'll use requirements.psd1
for modules in the PowerShell Gallery and the Modules folder for internal modules or modified public modules.
This is where I'll store my ARM templates or bicep or whatever, probably.
This is my actual test function! The folders will be named like this:
- vm
- storage
While the routes will look like this
- /vm/get
- /vm/new
- /vm/stop
- /storage/create
- /storage/delete
PREVIOUSLY, my folder structure looked like this
- vm
- vmNew
- vmStop
- storageCreate
- storageDelete
But then I found an awesome approach from Davide Mauri and adapted. All methods are stored in one folder/function and handled with a with a METHOD switch
.
This has a bunch of non-default values that I found in a repo and I imagine it'll come in handy. One day, I'll find out why they used these values haha.
I love that they enable profile loading! In my more advanced module, I used it to create a few quick functions. I also used it to explicitly load a slow-loading module and set some things like $PSDefaultParameterValues.
I don't need allllll of Az
so I'm just loading some that I'll imagine I'll need as a SQL Server-centric developer.
Hope this was helpful for you if you're new to all of this!
VS Code can run in a browser! It's wild. For me, I had to see it to understand so let's jump in with some screenshots.
First, click the green Code button then Create codespace on main
.
Then it starts building a container!
And now VS Code appears, whaaaat! Check out my browser tabs at the top.
Then if you want to see a list, you can go to your Codespaces page.
Here, you can even increase the size of your codespace. I haven't yet because I don't know what impact that has on the monthly free hours.
Alright, so next, let's get the Azure Functions App running by hitting debug.
Wait until a prompt that pops up saying that your application is running on port 7071 (when this screenshot was created, the folder was named vm and not greeting).
I'm including the whole VS Code screenshot because when I started doing functions, I wanted the tutorial to show what I'm supposed to be seeing.
Go ahead and Open in Browser
to be amazed!
Next up, we'll execute the function. Click the Azure Extension, expand the workspace tab at the bottom, then right click on greeting
. Then click Execute Function Now...
A prompt will appear in the settings bar at the top and you can change the default name of Azure
to anything you like.
I changed it to blog reader
. And here it is, successfully running!
:mindblown:
To use the devcontainer which contains everything you need, clone the repo however you do then open it in VS Code.
Ensure that the Dev Containers VS Code extension is installed. I imagine Docker alao has to be installed and running.
Once the extension is installed, a remote icon should appear in the lower-left hand corner.
Click on it, then a drop-down will appear at the top. Select Reopen in Container
in the Dev Containers
group.
Code will restart as a devcontainer!
Woo! Now you're set and you can follow all the last few steps in the Codespaces section, starting after Alright, so next, let's get the Azure Functions App running by hitting debug.
because it's all the same :O