Quick tip: Preventing `Vagrant Destroy`

I’m using Vagrant to test out some scripts, and I really need to stop myself from destroying my caching proxy that I’m running in the test.

To do that, I’ve got this Vagrantfile:

Vagrant.configure("2") do |config|
  config.vm.define "caching-proxy" do |this|
    this.trigger.before :destroy do |trigger|
      trigger.info = "This machine is currently prevented from being destroyed. Please remove the trigger to be able to destroy it.
      trigger.abort = 1
    end
    this.vm.box = "ubuntu/jammy64"
    # etc
  end
  config.vm.define "normalnode" do |this|
    this.vm.box = "ubuntu/jammy64"
  end
end

Now when I try to run vagrant destroy caching-proxy I get:

==> caching-proxy: Running action triggers before destroy ...
==> caching-proxy: Running trigger...
==> caching-proxy: This machine is currently prevented from being destroyed. Please remove the trigger to destroy it.
==> caching-proxy: Vagrant has been configured to abort. Terminating now...

Running vagrant destroy normalnode I get:

    normalnode: Are you sure you want to destroy the 'normalnode' VM? [y/N] y
==> normalnode: Forcing shutdown of VM...
==> normalnode: Destroying VM and associated drives...

Perfect.

Featured image is “Explosion” by “Quinn Dombrowski” on Flickr and is released under a CC-BY-SA License.

Responding to AWS Spot Instance “Instance Actions” (like terminate and stop)

During some debugging of an issue with our AWS Spot Instances at work, a colleague noticed that we weren’t responding to the Instance Actions that AWS sends when it’s due to shut down a spot instance.

We had a bit of a poke around, and found that no-one seems to have a service solution to respond to these events, to shut things down cleanly… so I wrote a set of shell scripts and a SystemD service to react to them.

On the journey, I discovered that there is a metadata mocking service that AWS provides, I learned how to create both RPM and DEB packages with Github actions (still not got them into a repo yet though!) and found that my new employer is really nice because they let me write this and release it as open source 😀

So, if this seems like something that might help you, or perhaps you’ve found a better way of doing this, let me know!

A screen shot of the github organisation for the terminate-notice script (link)

Project logo: Target icons created by Freepik – Flaticon

"Apoptosis Network (alternate)" by "Simon Cockell" on Flickr

Multipass on Ubuntu with Bridged Network Interfaces

I’m working on a new project, and I am using Multipass on an Ubuntu machine to provision some virtual machines on my local machine using cloudinit files. All good so far!

I wanted to expose one of the services I’ve created to the bridged network (so I can run avahi-daemon), and did this by running multipass launch -n vm01 --network enp3s0 when, what should I see but: launch failed: The bridging feature is not implemented on this backend. OH NO!

By chance, I found a random Stack Overflow answer, which said:

Currently only the LXD driver supports the networks command on Linux.

So, let’s make multipass on Ubuntu use LXD! (Be prepared for entering your password a few times!)

Firstly, we need to install LXD. Dead simple:

snap install lxd

Next, we need to tell snap that it’s allowed to connect LXD to multipass:

snap connect multipass:lxd lxd

And lastly, we tell multipass to use lxd:

multipass set local.driver=lxd

Result?

user@host:~$ multipass networks
Name             Type      Description
enp3s0           ethernet  Ethernet device
mpbr0            bridge    Network bridge for Multipass

And when I brought my machine up with avahi-daemon installed and configured to broadcast it’s hostname?

user@host:~$ ip -4 addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
37: br-enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    inet 192.0.2.33/24 brd 192.0.2.255 scope global dynamic noprefixroute br-enp3s0
       valid_lft 6455sec preferred_lft 6455sec
user@host:~$ multipass list
Name         State       IPv4             Image
vm01         Running     203.0.113.15     Ubuntu 22.04 LTS
                         192.0.2.101
user@host:~$ ping vm01.local
PING vm01.local (192.0.2.101) 56(84) bytes of data.

Tada!

Featured image is “Apoptosis Network (alternate)” by “Simon Cockell” on Flickr and is released under a CC-BY license.

Releasing a Presentation on “An introduction to Multi-Factor Authentication (MFA)”

Over the last year, the team I was in had a routine that every week we’d have a briefing-come-good-news meeting. About a third of the way through the year the routine changed and it would be fronted by a presentation on a subject of interest to that team member. It was loosely encouraged to be relevant to the job, but it wasn’t a formal requirement.

For whatever reason, my slot got bumped (I think it was the day of the Queen’s funeral, but I’m not sure), and then I rescheduled myself for December… and the meeting was cancelled as we were having an in-person meetup. Bah.

Anyway, I wrote a deck, and planned to deliver it. That isn’t going to happen now, so I’m releasing it for anyone to read, or maybe even play Powerpoint Karaoke with.

Click here to visit the deck. Press “S” to see the speaker notes (you’ll need them!)
LEGO Optimus Prime set 10302 in vehicle mode, with the axe, jetpack and Energon cube on the table alongside the informational plaque.

My review of the LEGO Optimus Prime set 10302

My history

Earlier this year LEGO released the Optimus Prime set, 10302, and I said to my friends at the time “£150… It’s good, but is it that good? (Who am I kidding… Add to basket)” but, of course, I didn’t.

Later in the year, I was fortunate enough to visit Billund in Denmark, the home of LEGO, and I got to see the glory of the Optimus Prime set in the … plastic? Anyway, because I was there with hand luggage only, I couldn’t fit the box and set into the luggage, so I couldn’t get it abroad, and assumed that was it – no more Optimus for me.

Until Christmas Day arrives, and my (amazing, beautiful, best person ever) wife hands me a box… As I pick it up from her, I say “That… sounds like Lego” but I still haven’t put two-and-two together yet. I peel open the paper and my mouth drops. It’s this set.

See, I’m of the era that watched all the cartoons, had all the comics, and (thanks to, in hindsight, my verygenerous parents) had a few of the toys… so this is a true throwback for me.

The build itself

Jules and I agreed that today would be the “build our Lego sets” day, so as I started my breakfast I cracked open the box.

The Optimus Prime LEGO set (10302) box, showing the robot-form.
The Optimus Prime LEGO set (10302) box, showing the robot-form.
The rear of the box shows the transforming model, plus the accessories which come with the build.
The rear of the box shows the transforming model, plus the accessories which come with the build.
11 bags of the LEGO Optimus Prime set 10302, plus a cardboard envelope containing instructions and 5 stickers.
11 bags of the LEGO Optimus Prime set 10302, plus a cardboard envelope containing instructions and 5 stickers.

I set to work. Right from the outset, the classic windscreen was visible, and it’s a perfect opening section for the set, and builds the anticipation for the final build.

The result of the first bag of lego - the classic windscreen for Optimus which hides the Matrix of Leadership (built later)
The result of the first bag of lego – the classic windscreen for Optimus which hides the Matrix of Leadership (built later)

Several hours later (including a few rounds of Singstar and Buzz, lunch and dinner), Optimus Prime is complete.

The completed model, standing in robot form, with gun in hand. Axe and Energon cube on the table in front of the robot, and a plaque about Optimus Prime.
The completed model, standing in robot form, with gun in hand. Axe and Energon cube on the table in front of the robot, and a plaque about Optimus Prime.
LEGO Optimus Prime set 10302 with the axe in hand and the Energon Cube, gun and plaque on the table in front of Robot-Form Optimus Prime.
This time Optimus in Robot-Form, with the axe in hand.
LEGO Optimus Prime set 10302 features a detachable jetpack as part of the build.
And finally, in robot form, showing off the detachable jetpack.
LEGO Optimus Prime set 10302 in vehicle mode, with the axe, jetpack and Energon cube on the table alongside the informational plaque.
In vehicle mode, the gun is part of the wheel-train, but the axe, Energon cube and jetpack need to travel separately!

Conclusion

This was a great build, and I’m glad I got the set – I’ll have plenty of enjoyment from showing it off and transforming it. I’m also really impressed at the level of detail they’ve gone into (although, to be fair, the grand piano which I got for Jules last year also set this bar pretty high).

Total build time was probably about 6 hours all-in, mostly building by myself, although Emily weighed in and helped for a bit. There were just enough pieces in each bag to justify having a new bag, but probably could have reduced things down to about 6 bags without too many worries.

It’s mildly frustrating that the jetpack and axe aren’t somehow incorporated into the vehicle-form, and the axe does occasionally fall off or pull the arm off, but, eh… it’s just there for the battles, and he’d be more likely to just shoot the gun. These are definitely things I can live with, I just need to be sure I don’t lose them!

So, final review – glad I got it, and the build revealed a few Easter eggs about the build that I really appreciated. 9/10

Thinking of getting the set? Consider buying it through my referral link: Amazon UK

"Sensitive Species" by "Rennett Stowe" on Flickr

HOWTO: Do DynDNS-style (DDNS) updates with Terraform (without leaking your credentials in the console)

For some of my projects, I run a Dynamic DNS server service attached to one of the less-standard DNS Names I own, and use that to connect to the web pages I’m spinning up. In a recent demo, I noticed that the terraform “changes” log where it shows what things are being updated showed the credentials I was using, because I was using “simple” authentication, like this:

data "http" "ddns_web" {
  url = "https://my.ddns.example.org/update?secret=${var.ddns_secret}&domain=web&addr=192.0.2.1"
}

variable "ddns_secret" {
  default = "bob"
}

For context, that would ask the DDNS service running at ddns.example.org to create a DNS record for web.ddns.example.org with an A record of 192.0.2.1.

While this is fine for my personal projects, any time this goes past, anyone who spots that update line would see the credentials I use for this service. Not great.

I had a quick look at the other options I had for authentication, and noticed that the DDNS server I’m running also supports the DynDNS update mechanism. In that case, we need to construct things a little differently!

data "http" "ddns_web" {
  url             = "https://my.ddns.example.org/nic/update?hostname=web&myip=192.0.2.1"
  request_headers = {
    Authorization = "Basic ${base64encode("user:${var.ddns_secret}")}"
  }
}

variable "ddns_secret" {
  type      = string
  sensitive = true
  default   = "bob"
}

So now, we change the URL to include the /nic/ path fragment, we use different names for the variables and we’re using Basic Authentication which is a request header. It’s a little frustrating that the http data source doesn’t also have a query type or a path constructor we could have used, but…

In this context the request header of “Authorization” is a string starting “Basic” but then with a Base64 encoded value of the username (which for this DDNS service, can be anything, so I’ve set it as the word “user”), then a colon and then the password. By setting the ddns_secret variable as being “sensitive”, if I use terraform console, and ask it for the value of data.http.ddns_web I get

> data.http.ddns_web
{
  "body" = <<-EOT
  good 192.0.2.1
  
  EOT
  "id" = "https://my.ddns.example.org/nic/update?hostname=web&myip=192.0.2.1"
  "request_headers" = tomap({
    "Authorization" = (sensitive)
  })
  "response_body" = <<-EOT
  good 192.0.2.1
  
  EOT
  "response_headers" = tomap({
    "Content-Length" = "18"
    "Content-Type" = "text/plain; charset=utf-8"
    "Date" = "Thu, 01 Jan 1970 00:00:00 UTC"
    "Server" = "nginx"
    "Strict-Transport-Security" = "max-age=31536000; includeSubDomains"
    "X-Content-Type-Options" = "nosniff"
    "X-Xss-Protection" = "1; mode=block"
  })
  "url" = "https://my.ddns.example.org/nic/update?hostname=web&myip=192.0.2.1"
}
>

Note that if your DDNS service has a particular username requirement, this can also be entered, in the same way, by changing the string “user” to something like ${var.ddns_user}.

Featured image is “Sensitive Species” by “Rennett Stowe” on Flickr and is released under a CC-BY license.

Barcamp Manchester 2022 – my review

Outside of work, I attended my first technical event since the 2020 lockdown. A Barcamp is a community run conference where the attendees propose the talks they want to present. In past years, I have attended with an intention to speak, and in some cases present several talks.

This a a picture of “the grid” – the cards on the far left are the rooms in which talks occur, the cards on the top row are time slots in which talks were to be delivered, and the rest of the cards were proposed talks.
This picture by Martin Rue on Twitter and used with his kind permission.

The first talk I attended was a talk by Patrick Hurley who was asking people to propose ideas to that would benefit people in the north of England. These will be written up into a book and used as inspiration for projects big and small. Well worth a look! 100ideasnorth.org

I missed the next slot, as I was making my way around groups of people I’d not spoken to for a while, but then my next talk of interest was “I’m an AWS Engineer… Ask me Anything (we’re hiring)” by Martin. I didn’t know anyone else from AWS was going to be there, so I went, if only to introduce myself and say hi… Well, one thing lead to another, and then I found myself joining Martin answering questions from my perspective in a different part of the organisation. I loved this session, and I’m really glad to have caught up with Martin, if only because he was such a lovely man and so enthusiastic about everything he was talking about!

After that, I went to the talk about Living in Japan by Fran. This was a quick ad-hoc talk, but totally adorable because it was just completely heartfelt and had loads of questions that were answered with fab anecdotes about life at a ladies-only university outside Tokyo… the questions were really good too!

Over lunch and slot 5, I put together a talk about my boardgame collection, and agreed to co-host a talk about working at AWS with Martin again.

I also found myself in a small talk, hosted by Harper, the 8 year old daughter of one of the attendees, who was very keen to present her first talk. It was a beautiful and well delivered talk about the elements of a pen, her favourite pen and what each element is for. She also fielded questions from the audience with a lot of confidence and spoke with great authority. She was fab, and I made a point of letting everyone I saw know about her talk!

I went to a talk in Slot 6 called “Dude, where’s my meetup” – a slightly confused talk which seemed to be asking “what happened to all the groups that met up in Manchester”, while also not being clear whether it was asking “and who’s going to run them again now we’re all meeting back up” or “why haven’t they restarted” or “what is replacing them” or even “why aren’t the groups which have restarted making more noise about themselves to make them found”… I plugged the North West UK Tech Community and encouraged groups to register themselves on there, and individuals to help clean out older groups that have closed.

I gave my talk on the Board Game spreadsheet I have created, and got a great couple of ideas on categorising the games we have, and whether expansions should be games in their own right, or not. Several people took links away, and I picked up this great alternative version that I could be reusing!

Next up was the conversation with the audience about working at AWS. Lots of really good questions again, and Martin and I developed quite a good raport. I think we’ll be trying to do things like these talks again!

After that session, I spoke to one of the audience members from the AWS session about being recruited by AWS, and then to another person about how they were using AWS. I really enjoyed being part of a conversation about how other people see AWS – I know it’s a lot of my day job, but it was just a nice reassurance that it’s not just something I can do inside my working hours!

Then we had the wrap-up, and there were lots of claps and cheers for the organising team and for anyone who spoke at the event. Great work everyone!

Afterwards, I went to the pub with some people who used to go to Geekup (and it was glorious!) and then made my way home.

All in all, a great day, and I’m looking forward to the next one!

"matrix" by "Jordi nll" on Flickr

I’m on a #Podcast: “It’s a Chat Episode 53 – More! – #TheMatrix: Reloaded”

Yep, they let me back on! And so, following the last recording (probably around November-ish), we recorded the chat that became Episode 53 of the It’s a Chat podcast. Much like the last episode, we talk about things which are connected to The Matrix: Reloaded (and get about 1/3rd of the way through the film), and talk about other stuff as well. I talk a fair amount about the Animatrix and we briefly discuss the game (that I don’t think any of us played).

I reference a series of books which I read, that I referred to as “Sand” but instead actually meant the first book, called “Wool” of the series “Silo“, which I rated 3/5 on Goodreads.

I also draw a conclusion that Agent Smith is actually rickrolling us, four years before rickrolling was a thing.

Featured image is “Matrix” by “Jordi nll” on Flickr and is released under a CC-BY-SA license.

"matrix" by "Dako Huang" on Flickr

I’m on a #Podcast: “It’s a Chat Episode 52 – Red Pill, Blue Pill – #TheMatrix”

A few months ago, I recorded a podcast episode with the “It’s a Chat Podcast” team, talking about The Matrix. We’ve got some subsequent episodes recorded about the sequels, but they’ve not been released yet! The first episode was released today as “Red Pill, Blue Pill – The Matrix“.

“It’s a Chat” talk about films, tv series and even games that were large parts of our lives growing up, including Star Wars, Ghostbusters, Indiana Jones and Star Trek.

Listening back to the episode today, it’s clear that I’ve not got any better at stopping the “Um”ing – so… I guess I need to do something about that :)

I’d love to hear your views on my views about the show, so please comment below, reply to the tweet or just message me using one of the links under “Where to find me” on my blog!

Featured image is “Matrix” by “Dako Huang” on Flickr and is released under a CC-BY license.

"Catch and Release" by "Trish Hamme" on Flickr

Releasing files for multiple operating systems with Github Actions in 2021

Hi! Long time, no see!

I’ve been working on my Decision Records open source project for a few months now, and I’ve finally settled on the cross-platform language Rust to create my script. As a result, I’ve got a build process which lets me build for Windows, Mac OS and Linux. I’m currently building a single, unsigned binary for each platform, and I wanted to make it so that Github Actions would build and release these three files for me. Most of the guidance which is currently out there points to some unmaintained actions, originally released by GitHub… but now they point to a 3rd party “release” action as their recommended alternative, so I thought I’d explain how I’m using it to release on several platforms at once.

Although I can go into detail about the release file I’m using for Rust-Decision-Records, I’m instead going to provide a much more simplistic view, based on my (finally working) initial test run.

GitHub Actions

GitHub have a built-in Continuous Integration, Continuous Deployment/Delivery (CI/CD) system, called GitHub Actions. You can have several activities it performs, and these are executed by way of instructions in .github/workflows/<somefile>.yml. I’ll be using .github/workflows/build.yml in this example. If you have multiple GitHub Action files you wanted to invoke (perhaps around issue management, unit testing and so on), these can be stored in separate .yml files.

The build.yml actions file will perform several tasks, separated out into two separate activities, a “Create Release” stage, and a “Build Release” stage. The Build stage will use a “Matrix” to execute builds on the three platforms at the same time – Linux AMD64, Windows and Mac OS.

The actual build steps? In this case, it’ll just be writing a single-line text file, stating the release it’s using.

So, let’s get started.

Create Release

A GitHub Release is typically linked to a specific “tagged” commit. To trigger the release feature, every time a commit is tagged with a string starting “v” (like v1.0.0), this will trigger the release process. So, let’s add those lines to the top of the file:

name: Create Release

on:
  push:
    tags:
      - 'v*'

You could just as easily use the filter pattern ‘v[0-9]+.[0-9]+.[0-9]+’ if you wanted to use proper Semantic Versioning, but this is a simple demo, right? 😉

Next we need the actual action we want to start with. This is at the same level as the “on” and “name” tags in that YML file, like this:

jobs:
  create_release:
    name: Create Release
    runs-on: ubuntu-latest
    steps:
      - name: Create Release
        id: create_release
        uses: softprops/action-gh-release@v1
        with:
          name: ${{ github.ref_name }}
          draft: false
          prerelease: false
          generate_release_notes: false

So, this is the actual “create release” job. I don’t think it matters what OS it runs on, but ubuntu-latest is the one I’ve seen used most often.

In this, you instruct it to create a simple release, using the text in the annotated tag you pushed as the release notes.

This is using a third-party release action, softprops/action-gh-release, which has not been vetted by me, but is explicitly linked from GitHub’s own action.

If you check the release at this point, (that is, without any other code working) you’d get just the source code as a zip and a .tgz file. BUT WE WANT MORE! So let’s build this mutha!

Build Release

Like with the create_release job, we have a few fields of instructions before we get to the actual actions it’ll take. Let’s have a look at them first. These instructions are at the same level as the jobs:\n create_release: line in the previous block, and I’ll have the entire file listed below.

  build_release:
    name: Build Release
    needs: create_release
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        include:
          - os: ubuntu-latest
            release_suffix: ubuntu
          - os: macos-latest
            release_suffix: mac
          - os: windows-latest
            release_suffix: windows
    runs-on: ${{ matrix.os }}

So this section gives this job an ID (build_release) and a name (Build Release), so far, so exactly the same as the previous block. Next we say “You need to have finished the previous action (create_release) before proceeding” with the needs: create_release line.

But the real sting here is the strategy:\n matrix: block. This says “run these activities with several runners” (in this case, an unspecified Ubuntu, Mac OS and Windows release (each just “latest”). The include block asks the runners to add some template variables to the tasks we’re about to run – specifically release_suffix.

The last line in this snippet asks the runner to interpret the templated value matrix.os as the OS to use for this run.

Let’s move on to the build steps.

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Run Linux Build
        if: matrix.os == 'ubuntu-latest'
        run: echo "Ubuntu Latest" > release_ubuntu
      
      - name: Run Mac Build
        if: matrix.os == 'macos-latest'
        run: echo "MacOS Latest" > release_mac

      - name: Run Windows Build
        if: matrix.os == 'windows-latest'
        run: echo "Windows Latest" > release_windows

This checks out the source code on each runner, and then has a conditional build statement, based on the OS you’re using for each runner.

It should be fairly simple to see how you could build this out to be much more complex.

The final step in the matrix activity is to add the “built” file to the release. For this we use the softprops release action again.

      - name: Release
        uses: softprops/action-gh-release@v1
        with:
          tag_name: ${{ needs.create_release.outputs.tag-name }}
          files: release_${{ matrix.release_suffix }}

The finished file

So how does this all look when it’s done, this most simple CI/CD build script?

name: Create Release

on:
  push:
    tags:
      - 'v*'

jobs:
  create_release:
    name: Create Release
    runs-on: ubuntu-latest
    steps:
      - name: Create Release
        id: create_release
        uses: softprops/action-gh-release@v1
        with:
          name: ${{ github.ref_name }}
          draft: false
          prerelease: false
          generate_release_notes: false

  build_release:
    name: Build Release
    needs: create_release
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        include:
          - os: ubuntu-latest
            release_suffix: ubuntu
          - os: macos-latest
            release_suffix: mac
          - os: windows-latest
            release_suffix: windows
    runs-on: ${{ matrix.os }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Run Linux Build
        if: matrix.os == 'ubuntu-latest'
        run: echo "Ubuntu Latest" > release_ubuntu
      
      - name: Run Mac Build
        if: matrix.os == 'macos-latest'
        run: echo "MacOS Latest" > release_mac

      - name: Run Windows Build
        if: matrix.os == 'windows-latest'
        run: echo "Windows Latest" > release_windows

      - name: Release
        uses: softprops/action-gh-release@v1
        with:
          tag_name: ${{ needs.create_release.outputs.tag-name }}
          files: release_${{ matrix.release_suffix }}

I hope this helps you!

My Sources and Inspirations

Featured image is “Catch and Release” by “Trish Hamme” on Flickr and is released under a CC-BY license.