Setting Up This Website - Static Web Apps in Azure
This website, right now, is set up as a static web application in Azure. Why, you ask? Because it can be. Because it’s clever. Because it can be useful. Because it’s cheap. Because it’s easy. Mainly because it’s cheap and easy. I didn’t come about this initially and, actually, when I first started looking into this a few years ago the concept was still fairly new and Azure had most of the features I will end up using for this site in preview, if at all.
If you want to skip the preamble and get down to business go ahead and skip to Deploying a Hugo Site on Azure
Table Of Contents
The Roads Not Traveled
I’ve deployed some other sites through various mechanisms and I would like to point out, prior to getting started, why I don’t think that they completely make sense for my purposes but why they might make sense for others. The Internet, technology in general, and people in particular, are varied things. For a personal site thinking about these choices in any great depth may not have any appreciable impact, and in fact, there may be cases where they’re more appropriate but it’s worth having a brief discussion.
WSYWIG Content Management Systems
If I’m showing my age, I’m sorry, but WSYWIG (what you see is what you get) CMS products were where the web was moving towards when I got started in technology. It’s interesting that we’re seeing the rebound these days and there are a few reasons why. CMS systems, especially ones designed to intake human-readble designed content, generally aren’t great at automation. They are very popular, however, and once set up they allow people to generate their content quickly and easily. Marketplaces with themes ready-made make it simple to get something that looks nice enough and there is an entire industry based on developing templates on a contract basis so outsourcing is available. These systems also do a great job of managing all of the “back end” stuff. Some are very good at having multiple users contribute. You can do backups and restores with a few clicks (generally). You don’t need to do much, if at all, to get the cross links and all that functional. It’s good stuff. It does a lot but it does it at a cost.
Part of the cost is transparency. Generating your own templates can be more involved than what most people may wish to take on–PHP is one language in particular that I’ve not had the best experiences with and it may not be obvious what includes may be required to get the functionality you’d like if you’re trying to write your own template or plugin. Some CMS systems also won’t make rolling back changes terribly easy. Perhaps you broke something or accidentally deleted a post? You may be able to do a restore but there are potentially other changes that were made in the interim. Or maybe you can’t track if someone else made that change. Certain CMS may be better about this but it’s still doing a lot and that brings up the last con–they’re big. They’re relatively complicated systems that are constantly running jobs in the background and dynamically inserting their content and they can end up requiring more resources than would be ideal or just being generally slow. They are also commonly targeted for exploitation and with so many mechanisms, and the administration component being accessible from the Internet, there is generally a good amount of attack surface.
Hosted Web Services
Getting away from dynamic systems we have the OG static site. Again, showing my age, but in the Geocities era, if you didn’t pay for a cgi-capable hosting service, this is all you had. Hosted services have come down in price and gone up in capability significantly and many of the drawbacks that will be mentioned are addressable but they do require effort and may still carry forward their limitations. With many common plans for various hosting providers you can very easily integrate buying a domain and hosting as well as being able to run dynamic content (commonly PHP) within reasonable constraints for a low-cost plan.
You interact with a hosted web service using SFTP (FTP over SSH) which is much more secure than the previously-ubiquitous plain-text FTP protocol however authenticating is generally limited to username and password. Managing users for a hosting provider’s account can be tedious at best (non-existant at worst) and doesn’t have a lot of intelligence or technology behind it, which is what spawned the all-in-one nature of the modern CMS.
Enter the Modern Static Site
This isn’t new but it is, probably, the newest pattern to build a good site. There is an entire movement behind building these sites over at jamstack.org and, really, it makes a lot of sense. The J, A, and M in “Jamstack” stand for JavaScript, APIs, and Markup. JavaScript the enables rich and interactive experiences of the web and is able to interact with APIs to enhance sites and web apps with dynamic content. Markup is the language used to turn the pre-generated content, like this post, into a web site, without fiddling with HTML tags and the like. There’s value, as a business, in having generated static sites as they reduce the attack surface on the front end and allow for collaborative work using git in addition to the ability to heavily automate content generation. There is value, as an individual, in generally reducing complexity and making it easier to have a functional website.
There are a number of places that support deploying static sites like Netlify, GCP, AWS, and Azure. There are also a number of generators like Jekyll, Gatsby, and Hugo. The different platforms provide various services, supported generators, and integrations. The various generators provide support for various features and run on a (usually) specific programming language platform (e.g. Hugo is built with Go, get it? HuGo? and it uses the Go templating language) so you can find one that is geared to your wants and needs. I’ve chosen Hugo because it was the most straight-forward to begin with. I’ve since needed to adjust and merge templates to create different kinds of content and incorporate a certain amount of functionality but those are posts for another day.
Deploying a Hugo Site on Azure
There’s a few ways to go about this and I may be taking the road less travelled using Azure DevOps. I do have a GitHub account but I also do enjoy some of the aspects of Azure DevOps so I’m going in this direction. You can use whichever method you think works best for you–there is validity in using a GitHub account for many reasons. That’s just not what I’ve done here.
Setting up local files
You can’t just click go–you need a couple of things set up. Firstly, you need to have an Azure account spun up. Then you’ll need to have Azure DevOps going–and that was basically a couple of clicks. You’ll need some familiarity with git and I recommend Learn Git Concepts Not Commands as a primer (or a refresher–not judging). I did this on a macOS device so I used homebrew
to install everything. You can get it at brew.sh. There are a couple of things you may need:
- git (obviously)
- hugo (obviously)
- Git Credential Manager from Microsoft (
brew install git-credential-manager-core
). This may also install .Net core–I already had it installed so I’m not completely certain. It’s not required but it makes this process very simple.
Ideally, you would git init
the directory that you were going to use for your Hugo site so that when you did hugo new site
you could just track everything from jump. If you’ve already created a Hugo site you can use the following commands to make it a repo and add everything in there:
git init
git add -A
Easy enough. If you don’t have any idea where to start with this technology but are interested in trying it out to see what the workflow is like I’ll drop a script in here to get your local up to speed. Some lines are taken from the Hugo Quick Start page.
mkdir HugoOnAzure
cd HugoOnAzure
git init
hugo new site HugoOnAzure
# we need a theme
git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke
# no need to be cute just swap out the URL in config.toml using an editor
sed -i -e "s/example.org/yourdomain.com/g" config.toml
hugo new posts/my-first-post.md
# this will generate some info for your first post file
# there's a lot to this but it's a quickstart
# make sure to set 'draft' to false or to remove that line
sed -i -e "s/true/false/g" posts/my-first-post.md
# add something in there. this is 100% not the best way to edit files
echo "Hello world!" >> posts/my-first-post.md
Setting up Azure DevOps
- In the Azure DevOps portal, ensure you have an organization. From here, add a new project using that nifty
+ New Project
button at the top right. - Create a new repository by clicking the + right next to the name of your project under the Azure DevOps logo at the top left.
- If you didn’t uncheck “Add a .gitignore” then you’ll be greeted with an empty repo and no really good information on how to proceed. I’ll provide some of that information presently. Take note of the name of the organization that you created in Azure DevOps and copy the URL of the page you’re currently looking at because you’ll need this to set up the remote location for the repository. Replace “organization”, “project”, and “repo-name” with the values that you need.
git remote add origin https://organization@dev.azure.com/organization/project/_git/repo-name
git push -u origin --all
- If you did uncheck that then you’ll have some information on how to push an existing repository already filled in with your repo information. Copy and paste that stuff into your shell and you should be able to push everything up to Azure DevOps. This step is where the git credential manager comes in to play–it’ll open up a browser window for you to log in (2FA and all that if you have it set up and you should) and makes it very convenient to get authenticated.
Creating a static web app
Here, the metaphorical rubber meets the proverbial road. I’d create a resource group for this just for organization purposes but you do you.
- You’ll go ahead and type “Static Web App” in the search bar for the Azure Portal to get into the menu for creating the static web app. The important thing here is to select “Other” in the Source parameter as we are going to use Azure DevOps as the source. It’s set up by default for GitHub and this is a very good marketing move as it’s extremely likely that a lot of people are coming in from GitHub but unless your org is paying for GitHub it’s more likely that they’ve got Azure DevOps. The free tier is pretty beefy so jump on into it.
- Click on the
Go To Resource
button (yeah, I know) and, when you get there, click onManage Deployment Token
in the top menu for the “Overview” screen. If you leave the “Overview screen to look around this will disappear so keep your eyes peeled. - Go back to your repository in Azure DevOps and in the “Files” pane of the repository you should have a
Set up build
button at the top right (since you did definitely commit some files and push to your remote location in git like I mentioned in Setting up local files, right?).
Digging into the pipeline
Now it’s about to get real. We need to do a couple of scripty things. If you think this looks like a Docker buildfile I won’t disagree with you. The philosophy is the same here–a disposable environment that can build the thing. The thing, in this case, is our Hugo site. Based on your content and themes you may not need much at all. Some themes, however, require a little extra. That’s up to you to handle.
We need to drop that token in there as a variable. Go ahead and do that and name it TOKEN
. A sturdy name for a sturdy token. I chose to keep the token a secret and we’ll see whether or not it will bite me in the ass.
To write the pipeline file YAML script… well. The best way to do that, in my experience, is to see a working example. I grabbed this one from Unravelled Development. They’ve got a nifty feature here with a staging token and you should check out their post for more info on that (it’s very good):
trigger:
- main
stages:
- stage: 'GenerateHugo'
displayName: 'Generate hugo website'
jobs:
- job:
pool:
vmImage: ubuntu-latest
workspace:
clean: all
steps:
- checkout: self
displayName: 'Checkout repository including submodules'
submodules: true # true so Hugo theme submodule is checked out
- script: wget https://github.com/gohugoio/hugo/releases/download/v0.83.1/hugo_0.83.1_Linux-64bit.deb -O '$(Pipeline.Workspace)/hugo_0.83.1_Linux-64bit.deb'
displayName: Download Hugo v0.83.1 Linux x64
# 2. Installs Hugo executable
- script: sudo dpkg -i $(Pipeline.Workspace)/hugo*.deb
displayName: Install Hugo
- task: AzureStaticWebApp@0
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
inputs:
app_location: '/'
app_build_command: 'hugo'
output_location: '/public'
azure_static_web_apps_api_token: '$(TOKEN)'
- task: AzureStaticWebApp@0
condition: ne(variables['Build.SourceBranch'], 'refs/heads/main')
inputs:
app_location: '/'
app_build_command: 'hugo'
output_location: '/public'
azure_static_web_apps_api_token: '$(STAGING_TOKEN)'
# below added by Mark
# really good stuff here. Important to make sure that the submodules are checked out as that is the recommended way to store themes.
# themes are worth discussing but that's more complicated than this post needs to be. I'll get there, I promise.
# in plain text this build file checks out the repo, downloads and installs hugo on an ubunt container, and then
# builds your source material with the hugo installed in the container and deploys it to your web app using
# that token we dropped in there as a variable.
So I got a fun error: ##[error]No hosted parallelism has been purchased or granted. To request a free parallelism grant, please fill out the following form https://aka.ms/azpipelines-parallelism-request
. This feels like the Azure equivalent of “McFly, you bozo, those boards don’t work on water. Unless you’ve got powwwwaah.” I submitted a request but I don’t know when it will come through. I’m writing this stream of consciousness now to see what the feeling of it is like. I’m not even sure what it means by “parallelism” because this seems relatively straightforward but maybe I’m missing something. After searching for a while it seems that you need to request “parallelism” for a private repo via that form. This script doesn’t feel terribly parallel but this is free-tier so it is what it is. Allegedly this can take days to be addressed.
30 MINUTES LATER Booooring. I know I just submitted the form about 20-30 minutes ago but yes, it’s very boring. The waiting, that is. Quite dull and uneventful. I haven’t come up with any great ideas just yet–waiting to get this website up. Yes, I’m using this process to make this very same website. Talk about dogfooding.
I’m actually very big on dogfooding. Not eating one’s dog’s food but “eating one’s own dogfood” which is an idiom that basically equates to “suffer what you cause others to suffer.” There is a symmetry there. An inherent empathy. I dig it. This will give me time to figure out the font problem I seem to be having with this template. It is very likely of my own making.
git config pull.rebase false
and then do a git.pull
on it to make a git push
work. Just a heads up. Also, it’s been a few hours and still waiting on that form to come back to me for my free tier of Azure DevOps build. Seriously, team, I know we want to prevent abuse but also c’mon now.Setting up a self-hosted agent
ONE DAY LATER Ok, I’m going to install a self-hosted agent. I have a Linux box that’s hanging out and running my asterisk server. It’s idle most of the time that it’s on so why not? Grabbing the agent from the page Deploy an Azure Pipelines agent on Linux. The vibe here is a lot like a Jenkins agent, if you’ve ever deployed one of those. The concepts here aren’t new but they are, possibly, put together in a novel manner. There’s a couple of things to do and several considerations to make–most of them are on that page but I’ll drop a script here to handle the files.
# this URL may change--the page linked above will have the latest
wget https://vstsagentpackage.azureedge.net/agent/2.196.1/vsts-agent-linux-x64-2.196.1.tar.gz
sudo adduser vstsagent #this is a little bit of a process but it should be straightforwward
cd /home/vstsagent
sudo mkdir agent
cd agent
sudo tar -xvf vsts-agent-linux-x64-2.196.1.tar.gz
The files are good to go in the home directory of the user vstsagent (ymmv based on what flavor you’re using but this is pretty common stuff nowadays). You’ll need a personal access token to register the agent. You can click on the person icon at the top right of the page in Azure DevOps and go down to personal access tokens. From there expand all scopes and pick Agent Pools (read and manage). If this seems a bit pedantic… it isn’t. It’s extremely important to understand that a PAT is basically a login and password put together. So yes, please ensure that the scope here is correct and don’t go making PATs at random. Also, and I know it tells you but it bears repeating. Save the PAT until you’re done with it (thanks, Blake Snyder) because you can’t get it back. You’ll need to generate another one.
su vstsagent # you remember the password you gave this user, right?
./config.sh # it's got ascii art it's legit. loving the resurgence of the command line
Default
it really means it will put the agent in the pool called Default
. If you made a pool already go ahead and type its name in there. I now have a pool called Default
and that’s fun, I guess. Going through the configuration process again you might be able to move it. At this stage it isn’t important but it’s worth noting in case you’re running up against issuesWe are now presented with the choice of running it as a service or on demand. I’m going to go ahead and run it as a service and I will do that by executing sudo ./svc.sh install vstsagent
and it will install and run as the user vstsagent which is not privileged on this device. You should definitely take a look at the script before running it in the event that some element there doesn’t work out for you or needs to be adjusted. You also may need to exit the vstsagent user session as, at least how I want it, that account cannot sudo. I didn’t have any issues and was able to start the agent manually by typing sudo ./svc.sh start
. It should automatically start on reboot.
Go back to your project and into Project Settings (it’s at the bottom left). Ensure that whatever pool you added this agent to is available to the project. I haven’t tried running the pipeline again yet but I’m hoping it would figure out that I had an agent available. Ok it complained and it still tried to go to the hosted pipeline. I added the following to the YAML file.
pool:
name: Default
# this 'pool' section already exists in the yaml posted above.
# just add the name entry to it
# this name may also be case-sensitive.
It’s now asking for permission to access a resource. It will let you know in the pipelines area and you can permit it with a click which is nice. It looks like this is to ensure you don’t run the pipeline on something you shouldn’t be.
Ok great the bash script tanked. Let me actually look at this pipeline and adjust it. I may already have Hugo installed on that system anyway. The error was at the installing Hugo step and that makes a whole lot of sense since it didn’t seem to spin up the docker image like I thought it would. So now it failed at running Docker.
Starting: AzureStaticWebApp
==============================================================================
Task : Deploy Azure Static Web App
Description : [PREVIEW] Build and deploy an Azure Static Web App
Version : 0.194.2
Author : Microsoft Corporation
Help : https://aka.ms/swadocs
==============================================================================
/bin/bash /home/vstsagent/agent/_work/_tasks/AzureStaticWebApp_18aad896-e191-4720-88d6-8ced4806941a/0.194.2/launch-docker.sh
docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/create": dial unix /var/run/docker.sock: connect: permission denied.
See 'docker run --help'.
##[error]Error: The process '/bin/bash' failed with exit code 126
Good stuff. I already added vstsagent to the docker group with sudo usermod -a -G docker vstsagent
. Let’s see if a therapeutic reboot may help.
Reboot did the trick. I probably just needed to restart systemctld but I took the easy way out. Pipeline works! So that’s how you make a pipeline and it feels good to be green But there’s still a few more things we need to do to make it work work. The website is there but we need to adjust a few things because our static web app has a boosted URL.
Summary
A lot happened in this post. We got set up with Azure DevOps and a local set of files to then put in to a repository in Azure DevOps. We created a Static Web Application. We then created a build pipeline to build the website and then deploy it. We also created the website and there has been few modifications from how it originally came (at the moment). There will be some posts on those. We had to take a detour for the CI agent as the free-tier agent takes manual approval of a request, so we went ahead and installed a Linux agent on a box I had at home. We also created a repository and checked in code (this actual website that you’re reading right now).
Good stuff. As to what’s next? Well, if you were following along and have now gone to visit your Hugo site you’ll find that it doesn’t look great. Style sheets aren’t loading, etc. It’s there but it isn’t quite functional. That’s what’s coming next.
Actually, found another problem
As I finished typing this, staged, committed, and pushed–I got another build failed. It succeeded a few hours ago.
1 error(s), 0 warning(s)
One or more errors occurred. (One or more errors occurred. (Access to the path '/home/vstsagent/agent/_work/1/s/public/posts/post-6/index.html' is denied.)) (One or more errors occurred. (Access to the path '/home/vstsagent/agent/_work/1/s/public/posts/index.html' is denied.)) (One or more errors occurred. (Access to the path '/home/vstsagent/agent/_work/1/s/public/posts/index.xml' is denied.)) (Access to the path '/home/vstsagent/agent/_work/1/s/public/posts/post-6/index.html' is denied.) (Access to the path '/home/vstsagent/agent/_work/1/s/public/posts/index.html' is denied.) (Access to the path '/home/vstsagent/agent/_work/1/s/public/posts/index.xml' is denied.)
Let’s take a peek.
drwxr-xr-x 15 vstsagent vstsagent 4.0K Dec 15 17:19 .
drwxr-xr-x 10 vstsagent vstsagent 4.0K Dec 15 17:19 ..
drwxr-xr-x 2 root root 4.0K Dec 15 12:15 about
drwxr-xr-x 2 root root 4.0K Dec 15 12:15 about-mark
drwxr-xr-x 2 root root 4.0K Dec 15 12:15 about-us
drwxr-xr-x 2 root root 4.0K Dec 15 12:15 archives
drwxr-xr-x 2 vstsagent vstsagent 4.0K Dec 15 12:14 categories
drwxr-xr-x 2 root root 4.0K Dec 15 12:15 contact
drwxr-xr-x 2 vstsagent vstsagent 4.0K Dec 15 12:14 css
drwxr-xr-x 2 vstsagent vstsagent 4.0K Dec 15 12:14 fonts
drwxr-xr-x 4 root root 4.0K Dec 15 12:14 images
drwxr-xr-x 2 vstsagent vstsagent 4.0K Dec 15 12:14 js
drwxr-xr-x 5 vstsagent vstsagent 4.0K Dec 15 12:15 page
drwxr-xr-x 11 root root 4.0K Dec 15 12:15 posts
drwxr-xr-x 12 vstsagent vstsagent 4.0K Dec 15 17:19 tags
I’ve been seeing some folders still have ownership of root/root. They still have read access so it should be fine but it’s also unusual. Perhaps we didn’t clean up well behind ourselves. I’m actually remembering reading something about how the non-Azure agents can cache things and I’m getting the vibe that this is the issue.
I say that yet the pipeline file already has clean: all
under workspace
which should very well be clearing any artifacts. What is going on? Well, clicking on the actual Job in the jobs area of the pipeline (drill down twice on “Job”) will tell you exactly which step failed and, in this case, it’s the “preparing build environment”. It looks like those permissions are not allowing for the build area to be cleaned. So let’s see if we can clean up after ourselves sufficiently.
root@markcentre:~# cd /home/vstsagent/
root@markcentre:/home/vstsagent# ls
agent vsts-agent-linux-x64-2.196.1.tar.gz
root@markcentre:/home/vstsagent# cd agent
root@markcentre:/home/vstsagent/agent# ls
bin config.sh _diag env.sh externals run-docker.sh run.sh runsvc.sh svc.sh _work
root@markcentre:/home/vstsagent/agent# cd _work
root@markcentre:/home/vstsagent/agent/_work# ls
1 2 node_modules SourceRootMapping _tasks _temp _tool
root@markcentre:/home/vstsagent/agent/_work# cd 1
root@markcentre:/home/vstsagent/agent/_work/1# ls
a b s TestResults
root@markcentre:/home/vstsagent/agent/_work/1# cd ..
root@markcentre:/home/vstsagent/agent/_work# mv ./1 ..
root@markcentre:/home/vstsagent/agent/_work# ls
2 node_modules SourceRootMapping _tasks _temp _tool
root@markcentre:/home/vstsagent/agent/_work# mv ./2 ..
I moved the folders in question up just to make sure I had them if I still needed them. When those folders are gone the build executes just fine. It feels like there’s some contention as to the context of the file creation/modification. It would probably be best to just get rid of everything prior to finishing the task, but after deploying. Let’s take a look at that YAML pipeline file and see what we can do.
Investigating the pipeline
To be clear, this shouldn’t need to happen but we’re operating under specific circumstances. This is both the good part and the bad part about IT. In this situation I am not running the Azure build agent as root. To be even more specific–I’m not running a container in my build either however the AzureStaticWebApp task runs a file called launch-docker.sh to do the magic that it does. Even though I don’t need a container to build the site. It’s pretty cool that it runs a docker image with Oryx to actually do all the building of the image. This allows for a very modular approach where the code-building black box can be updated independently and I dig it.
This isn’t transparent to the end-user in the Azure DevOps pipeline, however, but it was interesting to learn regardless. Docker, by default, will write to disk as root which is where I’m running into permissions issues. It turns out that docker run
has an argument for user. Perhaps this will work. I adjusted the launch-docker.sh to have that –user argument:
docker run --user "$(id -u):$(id -g)"\
--env-file ./env.list \
-v "$SWA_WORKING_DIR:$SWA_WORKSPACE_DIR" \
"$SWA_DEPLOYMENT_CLIENT" \
./bin/staticsites/StaticSitesClient run
In using the id
command it should, theoretically, Just Work. It did not. Popped off with an “unexpected error” once it was in the container. I’m getting the vibe that the container needs root for something that it does. That’s annoying.
Resolution or acceptance
It turns out that Oryx will throw an unspecified error any time the build script is run on a container with a non-root user specified in –user and that’s unfortunate. I’ll be submitting an enhancement request for that. I did work around this, though. The “straightforward” way is to simply just not build the application in the container but still use it to deploy the app. I haven’t actually drilled down into what that process is like so I’m still using the black box AzureStaticWebApp task. But I have an entire Linux box. The pipeline originally started with actually installs Hugo (like it would do on a container) but I’ve found out two things: since I’m not running the agent as root I simply can’t install or update Hugo as part of this process and I can do it with Homebrew. Well, I can install it with Homebrew, at least. So I did.
Aaaaand it didn’t work. ##[error]Bash exited with code '127'.
is what I got. Turns out that when command line tasks run they run with –noprofile and –norc and that’s what Homebrew uses to set up your path (Homebrew sets everything up in the user’s folders and doesn’t require root). Ok cool no problem we just use the full path. That works but how do we get around the AzureStaticWebApp step? There’s a parameter called skip_app_build
that will do it for us. For that step you can see something approaching the following in the output:
App Directory Location: '/public' was found.
No Api directory specified. Azure Functions will not be created.
Looking for event info
Skipping step to build /working_dir/public with Oryx
Either no Api directory was specified, or the specified directory was not found. Azure Functions will not be created.
Zipping App Artifacts
Done Zipping App Artifacts
Uploading build artifacts.
Finished Upload. Polling on deployment.
It’s the final pipeline 🎹
trigger:
- main
stages:
- stage: 'GenerateHugo'
displayName: 'Generate hugo website'
jobs:
- job:
pool:
name: Default
workspace:
#clean: all
steps:
- checkout: self
displayName: 'Checkout repository including submodules'
submodules: true # true so Hugo theme submodule is checked out
- task: CmdLine@2
displayName: "Hugo build"
#command line runs with --noprofile --norc and that's annoying
inputs:
script: '/home/vstsagent/.linuxbrew/bin/hugo'
- task: AzureStaticWebApp@0
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
inputs:
skip_app_build: true
app_location: '/public'
app_build_command: ''
output_location: ''
azure_static_web_apps_api_token: '$(TOKEN)'
If “The Final Countdown” is playing in your head then I have succeeded.
Lessons from the Pipeline
The real pipeline is the challenges we discovered along the way. You’ll note that the staging task is not present and that’s in the interest of clarity as well as because I don’t have a staging site up just yet. That’s going to tie in with the other stuff in the next post. This pipeline also will only work on a self-hosted agent. This might be what you need to do for reasons (in my case it was because I didn’t have that delicious free-tier grant) but it can really be a pain in the ass. It’s almost always better to KISS and this is entire post is an intentionally self-inflicted example of that. Not running the agent as root is really what made this difficult–not even because the build didn’t finish but because it couldn’t clear out the artifacts for the next build. Hugo, itself, doesn’t clear out that directory either so just rolling with it wouldn’t be advisable–you might end up using more storage than necessary. You could have a cron job continuously chown
ing the directory. You could spin up a container to clean the directory and then the container to deploy. You could do a lot of things but they’re all elements that add complexity. So keep it simple, stupid. If you don’t have an actual reason to run a self-hosted agent, don’t. Sometimes money is that reason and that’s ok. As long as there is a reason.
The real lesson, though, is to read better. Really read things. I read a few Issues on the Hugo Github about cleaning the output directory (and this makes total sense to me as well as other people) and they basically said “Hugo doesn’t clean those files. Figure it out, nerd.” I’m embellishing, but only slightly. I didn’t continue looking because I mistook confidence for fact. The Hugo Basic Usage page mentions a “flag” and yes, reader, there is indeed a --cleanDestinationDir
flag. I genuinely don’t know how I missed that flag searching for ‘hugo clean’ but now that I’ve seen it it’s coming up ahead of the Issues pages. Thanks, search engines.
What’s happening now is that /public
can’t be cleared when the repo checks out so I need to finally add that directory to my .gitignore. I have seen plenty of /public directories in repos for themes but I don’t really need to have it tracked in versioning (a statement I may very well regret at a later date). Just adding that to the build command (re-enabling the build in the AzureStaticWebApp task) and removing the workspace cleaning stuff. Since it’s a docker container that’s building the app I’m not worried about cruft in the OS. There are actually several ways that the repo gets cleaned and I’m just going to go through all of them and make sure they’re disabled. It didn’t work, regardless. I got a chunky new error: ##[warning]Unable move and reuse existing repository to required location.
. I’m going to submit that enhancement request to be able to pass a script or something to the Oryx container so that we can clean up after ourselves if need be. We’ll see how snarky people get about it.
If you stuck with me for this roller coaster (or if you were yelling at me because you already thought of a solution), thanks! It’s been fun. Now on to part 2 where I’m gonna fix the site up a little more. Make it feel like home. 14 errors, 119 warnings and 0 suggestions
./content/posts/setting-up-this-website.md 10:283 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 10:356 warning Consider removing 'fairly'. Microsoft.Adverbs 10:401 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 17:62 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 17:90 warning Consider using 'before' Microsoft.Wordiness instead of 'prior to'. 17:119 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 17:170 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 20:4 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 20:16 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 20:24 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 20:134 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 20:186 warning Try to avoid using Microsoft.We first-person plural like 'we'. 20:335 warning Consider removing 'generally'. Microsoft.Adverbs 20:373 error Use 'they're' instead of 'They Microsoft.Contractions are'. 20:382 warning Consider removing 'very'. Microsoft.Adverbs 20:465 warning Consider removing 'quickly'. Microsoft.Adverbs 20:477 warning Consider removing 'easily'. Microsoft.Adverbs 20:733 warning Consider using 'all' instead Microsoft.Wordiness of 'all of'. 20:771 warning Consider removing 'very'. Microsoft.Adverbs 20:869 warning Consider removing 'generally'. Microsoft.Adverbs 22:408 warning Consider removing 'terribly'. Microsoft.Adverbs 22:454 warning Consider removing Microsoft.Adverbs 'accidentally'. 22:531 warning Consider removing Microsoft.Adverbs 'potentially'. 22:966 warning Consider removing 'generally'. Microsoft.Adverbs 22:982 error Use 'they're' instead of 'They Microsoft.Contractions are'. 22:1138 warning Consider removing 'generally'. Microsoft.Adverbs 25:35 warning Try to avoid using Microsoft.We first-person plural like 'we'. 25:35 error Use 'we've' instead of 'we Microsoft.Contractions have'. 25:68 error More than 3 commas! marktoso.TresComas 25:78 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 25:466 warning Consider removing 'very'. Microsoft.Adverbs 25:471 warning Consider removing 'easily'. Microsoft.Adverbs 27:100 warning ' previously-' doesn't need a Microsoft.Hyphens hyphen. 27:173 warning Consider removing 'generally'. Microsoft.Adverbs 30:20 error Use 'it's' instead of 'it is'. Microsoft.Contractions 30:184 warning Consider removing 'really'. Microsoft.Adverbs 30:222 error More than 3 commas! marktoso.TresComas 30:793 warning Consider removing 'heavily'. Microsoft.Adverbs 30:867 warning Consider removing 'generally'. Microsoft.Adverbs 32:383 error Use 'for example' instead of Microsoft.Foreign 'e.g.'. 32:485 error Use 'that's' instead of 'that Microsoft.Contractions is'. 35:40 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 35:131 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 35:148 warning Consider using 'some' instead Microsoft.Wordiness of 'some of the'. 35:187 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 38:350 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 38:538 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 41:150 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 41:224 warning Consider removing 'very'. Microsoft.Adverbs 69:96 warning Consider removing 'really'. Microsoft.Adverbs 69:276 warning For a general audience, use Microsoft.GeneralURL 'address' rather than 'URL'. 69:403 error Punctuation should be inside Microsoft.Quotes the quotes. 69:419 error Punctuation should be inside Microsoft.Quotes the quotes. 74:436 warning Consider removing 'very'. Microsoft.Adverbs 78:1 error Use 'we're' instead of 'we Microsoft.Contractions are'. 78:244 warning Try to avoid using Microsoft.We first-person plural like 'we'. 78:342 warning Consider removing 'very'. Microsoft.Adverbs 78:375 warning Consider removing 'extremely'. Microsoft.Adverbs 79:47 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 80:260 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 83:29 warning Try to avoid using Microsoft.We first-person plural like 'We'. 83:118 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 83:258 warning Try to avoid using Microsoft.We first-person plural like 'our'. 85:153 warning Consider removing 'really'. Microsoft.Adverbs 85:216 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 85:269 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 87:1 warning Try to avoid using Microsoft.We first-person plural like 'We'. 87:127 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 87:167 warning Try to avoid using Microsoft.We first-person plural like 'we'. 87:177 warning Consider using 'whether' Microsoft.Wordiness instead of 'whether or not'. 87:205 warning Use first person (such as Microsoft.FirstPerson 'me') sparingly. 89:39 warning In general, don't use an Microsoft.Ellipses ellipsis. 89:77 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 89:120 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 89:359 warning Consider removing 'very'. Microsoft.Adverbs 135:3 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 135:356 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 135:397 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 135:469 error Use 'it's' instead of 'it is'. Microsoft.Contractions 135:481 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 135:586 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 135:746 warning Consider removing 'terribly'. Microsoft.Adverbs 135:789 error Use 'it's' instead of 'it is'. Microsoft.Contractions 135:800 error Use 'it's' instead of 'it is'. Microsoft.Contractions 138:18 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 138:51 error Use an en dash in a range of Microsoft.RangeFormat numbers. 138:51 warning In most cases, use 'from' or Microsoft.Ranges 'through' to describe a range of numbers. 138:83 warning Consider removing 'very'. Microsoft.Adverbs 138:109 error Use 'that's' instead of 'that Microsoft.Contractions is'. 138:144 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 138:231 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 138:268 warning Consider removing 'very'. Microsoft.Adverbs 140:1 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 140:14 warning Consider removing 'very'. Microsoft.Adverbs 140:256 warning Use first person (such as Microsoft.FirstPerson 'me') sparingly. 140:294 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 140:335 error Use 'it's' instead of 'It is'. Microsoft.Contractions 140:341 warning Consider removing 'very'. Microsoft.Adverbs 140:356 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 142:51 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 142:107 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 142:151 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 142:348 warning Use first person (such as Microsoft.FirstPerson 'me') sparingly. 142:355 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 142:391 warning Consider removing 'Seriously'. Microsoft.Adverbs 142:407 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 142:415 warning Try to avoid using Microsoft.We first-person plural like 'we'. 144:30 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 145:21 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 145:56 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 145:62 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 145:113 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 145:198 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 145:245 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 145:391 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 145:469 error Use 'they're' instead of 'they Microsoft.Contractions are'. 155:71 warning Prefer 'YMMV' over 'ymmv'. marktoso.Substitue 155:205 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 155:367 warning Prefer 'personal digital Microsoft.Terms assistant' over 'Agent'. 155:428 warning In general, don't use an Microsoft.Ellipses ellipsis. 155:447 warning Consider removing 'extremely'. Microsoft.Adverbs 155:640 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 160:84 warning Consider removing 'really'. Microsoft.Adverbs 160:113 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 160:212 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 160:263 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 162:1 error Use 'we're' instead of 'We Microsoft.Contractions are'. 162:1 warning Try to avoid using Microsoft.We first-person plural like 'We'. 162:79 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 162:128 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 162:247 error Use 'isn't' instead of 'is Microsoft.Contractions not'. 162:347 warning Consider using 'if' instead of Microsoft.Wordiness 'in the event that'. 162:503 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 162:528 error Use 'can't' instead of Microsoft.Contractions 'cannot'. 162:540 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 162:592 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 164:119 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 164:208 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 164:243 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 164:253 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 164:335 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 174:38 warning Use first person (such as Microsoft.FirstPerson 'me') sparingly. 174:275 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 189:12 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 189:103 warning Try to avoid using Microsoft.We first-person plural like 'Let's'. 191:71 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 191:213 warning Try to avoid using Microsoft.We first-person plural like 'we'. 191:274 warning Try to avoid using Microsoft.We first-person plural like 'we'. 191:313 warning Try to avoid using Microsoft.We first-person plural like 'our'. 191:346 warning For a general audience, use Microsoft.GeneralURL 'address' rather than 'URL'. 194:30 warning Try to avoid using Microsoft.We first-person plural like 'We'. 194:135 warning Try to avoid using Microsoft.We first-person plural like 'We'. 194:172 warning Try to avoid using Microsoft.We first-person plural like 'We'. 194:246 warning Try to avoid using Microsoft.We first-person plural like 'We'. 194:391 warning Try to avoid using Microsoft.We first-person plural like 'We'. 194:426 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 194:449 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 194:494 warning Try to avoid using Microsoft.We first-person plural like 'we'. 194:530 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 194:544 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 199:3 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 204:1 warning Try to avoid using Microsoft.We first-person plural like 'Let's'. 222:146 warning Try to avoid using Microsoft.We first-person plural like 'we'. 222:188 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 222:283 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 224:1 warning Use first person (such as 'I Microsoft.FirstPerson ') sparingly. 224:90 warning Consider removing 'very'. Microsoft.Adverbs 224:127 error Use 'what's' instead of 'What Microsoft.Contractions is'. 224:307 error Punctuation should be inside Microsoft.Quotes the quotes. 224:370 error Use 'aren't' instead of 'are Microsoft.Contractions not'. 224:424 warning Try to avoid using Microsoft.We first-person plural like 'let's'. 224:437 warning Try to avoid using Microsoft.We first-person plural like 'we'. 244:1 warning Use first person (such as 'I Microsoft.FirstPerson ') sparingly. 244:53 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 244:67 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 244:294 warning Consider using 'before' Microsoft.Wordiness instead of 'prior to'. 244:346 warning Try to avoid using Microsoft.We first-person plural like 'Let's'. 244:404 warning Try to avoid using Microsoft.We first-person plural like 'we'. 247:50 warning Try to avoid using Microsoft.We first-person plural like 'we'. 247:167 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 247:203 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 247:244 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 247:275 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 247:405 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 247:606 warning Consider removing 'very'. Microsoft.Adverbs 247:694 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 249:185 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 249:363 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 257:67 error Use 'didn't' instead of 'did Microsoft.Contractions not'. 257:144 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 260:442 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 260:499 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 260:659 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 260:679 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 260:692 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 260:758 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 260:855 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 262:72 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 262:307 warning Try to avoid using Microsoft.We first-person plural like 'we'. 262:356 warning Try to avoid using Microsoft.We first-person plural like 'we'. 262:462 warning Try to avoid using Microsoft.We first-person plural like 'us'. 311:54 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 314:37 warning Try to avoid using Microsoft.We first-person plural like 'we'. 314:100 error Use 'isn't' instead of 'is Microsoft.Contractions not'. 314:171 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 314:328 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 314:385 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 314:407 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 314:465 warning Consider removing 'really'. Microsoft.Adverbs 314:714 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 314:731 warning Consider removing 'really'. Microsoft.Adverbs 314:1336 warning Prefer 'personal digital Microsoft.Terms assistant' over 'agent'. 316:45 warning Consider removing 'Really'. Microsoft.Adverbs 316:173 warning Use first person (such as Microsoft.FirstPerson 'me') sparingly. 316:280 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 316:350 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 316:707 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 318:84 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 318:125 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 318:205 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 318:214 warning Consider removing 'really'. Microsoft.Adverbs 318:271 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 318:278 warning Consider removing 'very'. Microsoft.Adverbs 318:295 warning Consider using 'later' instead Microsoft.Wordiness of 'at a later date'. 318:503 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 318:757 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 318:786 warning Consider using 'all' instead Microsoft.Wordiness of 'all of'. 318:962 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 318:1078 warning Try to avoid using Microsoft.We first-person plural like 'we'. 318:1122 warning Try to avoid using Microsoft.We first-person plural like 'We'. 320:19 warning Use first person (such as Microsoft.FirstPerson 'me') sparingly. 320:166 warning Use first person (such as Microsoft.FirstPerson 'I'm') sparingly. 324:210 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly.✖ 26 errors, 207 warnings and 0 suggestions in 1 file.