Skip to content

James Williams

Packing Lucia for a four-season motorcycle camping trip

Part of the Far West 2024 series.

The objective today is to cut as much useless cruft from the packing list as possible. I need to outfit myself to ride through very hot temperatures in Southern California and Arizona, and very cold temperatures in Wyoming and Montana. It鈥檚 only May and even in the more temperate Pacific Northwest, overnight temperatures dip quite low. I also have a few days scheduled with friends in Palm Springs, so I鈥檒l need enough kit to look presentable in regular society (to the extent that that鈥檚 possible for me).

I need some basic tools to fix a puncture or adjust the suspension. This is America, so a CAA/AMA membership should negate the need for spare parts, but I would want to bring spare shift/clutch/brake levers, oil filters and other model-specific items if venturing further.

Lots of shit on a rug.

Behold, lots of shit on a rug.

Let鈥檚 break it down by bag. I have five pieces of soft luggage that strap to the bike and provide about 84 litres of capacity in total. I can also strap bulkier items like my tent on top of the luggage.

Kriega OS-12 x 2 saddlebags

These are positioned lower and more central on the bike, so I鈥檒l put the heaviest and least fragile items in here like tools and camping hardwear. These bags are also easier to access without unstrapping anything, so I put a few quick-access things here as well, just minding that if I hit the dirt, anything breakable in the side luggage will get a little smashy. Likewise, it鈥檚 not a good place for a first aid kit assuming if I fall and hurt myself, whatever is in the side luggage will be underneath a 450 lb. motorcycle and difficult to access.

Right-hand bag

  • Primus burner
  • Fuel canister
  • Stainless pot/plate set
  • Small skillet
  • Ceramic mug
  • Collapsible pour-over coffee filter
  • Salt, pepper, olive oil, ground coffee
  • Fork, spoon, pocket knife
  • Tongs (you laugh, but you鈥檝e never known the pain of dropping a nice cut of steak in a fire pit)
  • Camp soap and scrubber
  • Small garbage bag
  • Roll of toilet paper
  • Shower flip-flops
  • Tent footprint/light tarp
  • Kindle

Left-hand bag

  • Basic tool roll
  • Multi allen key tool (Ducati likes allen keys)
  • Stop & Go T-Handle Tubeless tire repair kit
  • Stop & Go mini air compressor
  • Tire pressure gauge with deflator button
  • Chain lube (every thousand kilometres gotta keep that thing saucy)
  • Gorilla tape
  • Multi-tool pocket knife
  • Headlamp
  • Flashlight
  • Rope
  • 6 ft. webbing strap/tie-down Giant Loop Armadillo spare fuel bag, 2 gallon (Lucia has a small tank, especially for an 803 cc twin)
  • Ducati user manual
  • Summer riding gloves
  • Toque
  • Cap

Kriega US-30

Large tail bag. I鈥檒l put my clothing in here as well as my laptop, and I鈥檒l try to avoid heavy items to keep the weight centered over the footpegs as much as possible.

  • Jeans
  • Shorts
  • Buttoned shirt
  • Lightweight hoodie
  • T-shirts x 4
  • Thermal base layer, top and bottom
  • Socks and underwear x 5
  • Swim trunks
  • Quick-drying travel towel
  • Laptop and peripherals

Kriega US-20

Medium-sized tank bag. I鈥檒l put soft camping items and miscellaneous bulky items in here. More weight over the tank helps keeps the bike stable under load.

  • Medical kit
  • Lightweight down sleeping bag
  • Inflatable sleeping mat
  • Inflatable pillow
  • Down quilt (for such a range in temperatures, it鈥檚 easier and more versatile to do a light sleeping bag and layer on a quilt when needed)
  • Arc鈥橳eryx low-top hiking boots/shoes
  • iPad

Honestly, shoes are the toughest thing to reason about. They are both bulky and heavy, and for three weeks of varied activity, including dinners and nights out, I would love to bring a pair of sneakers that I can dress up more. I just don鈥檛 have the space, but I recognize I may need to keep an eye out for a pair of shoes along the way that collapse to a small size but can look sharp in a pinch.

Kriega US-10

Small tail bag that can attach to the larger ones. The US-30 in this case. I leave some room in this one for groceries.

  • Water bottle
  • Light toiletry bag
  • Patagonia Nano-Puff jacket

Attached items

Strapped directly to the tail using Rok Straps over the US-30 are the tent, hatchet, and light folding chair. Opulent, I know.

I am going to bring my Hippo Hands hand guards on this trip given the likelihood of rain and certainty of cool temperatures in the mountains. They鈥檒l be awkward in the hotter climates, but they鈥檙e too large to pack away so I think it鈥檚 easiest to just fix them on at the outset and leave 鈥檈m.

I鈥檒l keep my FUJIFILM X-T10 camera over my shoulder generally, and a GoPro Hero 11 mounted on a rigid mount above the wind screen facing forward. I also have a Hero 7 on a flexible clamp mount I can use for B-roll. Rounding it out on the handlebars is a Garmin Zumo XT Navigator that frankly deserves an entire blog post of its own to unravel the dark wisdom of the Garmin software engineer.

I'm in love with Guanajuato

Guanajuato is my favourite city in Mexico. The centro historico is built into a valley, and many of the roads are tunneled through the mountains that surround it. The city itself has been built and rebuilt upon its former self, with a subterranean level comprised of the haciendas that once lined the Guanajatu River.

Guanajuato City facing south from Centro

Lens Model iPhone 14 Pro back triple camera 6.86mm f/1.78
ISO 80
Exposure Time 1/121
Image Size 4032x3024
GPS Position 21.018317 N, 101.253494 W
GPS Altitude 2058.7 m Above Sea Level
GPS Img Direction 159.2975464
Create Date 2023:01:03 18:22:08
View full resolution 猡达笌

My first thought when I got here was, why? Why tunnel through three kilometers of mountain to build a town here? The answer is silver. The area around Guanajuato accounted for more than two-thirds of the world鈥檚 silver production leading into the 19th century. The primary mine鈥擫a Valenciana鈥攊s still operational.

Guanajuato was also the site of the first battle of the Mexican War of Independence. You can still see the bullet holes in the Alh贸ndiga de Granaditas (grain exchange building next to the central mercado).

Guanajuato is about 4 to 5 hours by bus from Mexico City and well worth the trip. It鈥檚 also a very reasonable day trip (and beautiful drive) from San Miguele de Allende.

Guanajuato City church in main plaza

Lens Model iPhone 14 Pro back triple camera 6.86mm f/1.78
ISO 80
Exposure Time 1/7576
Image Size 4032x3024
GPS Position 21.016833 N, 101.254242 W
GPS Altitude 2024 m Above Sea Level
GPS Img Direction 116.5262318
Create Date 2023:01:03 12:41:27
View full resolution 猡达笌

Volkswagen Beetle at sunset in Guanajuato City

Lens Model iPhone 14 Pro back triple camera 6.86mm f/1.78
ISO 80
Exposure Time 1/1779
Image Size 4032x3024
GPS Position 21.019817 N, 101.250581 W
GPS Altitude 2076.3 m Above Sea Level
GPS Img Direction 260.5706482
Create Date 2023:01:03 18:07:53
View full resolution 猡达笌

Guanajuato skyline facing northeast

Lens Model iPhone 14 Pro back triple camera 6.86mm f/1.78
ISO 80
Exposure Time 1/1241
Image Size 4032x3024
GPS Position 21.014539 N, 101.254303 W
GPS Altitude 2085.6 m Above Sea Level
GPS Img Direction 40.99349974
Create Date 2023:01:04 17:57:19
View full resolution 猡达笌

Subterranean intersection in Guanajuato

Lens Model iPhone 14 Pro back triple camera 6.86mm f/1.78
ISO 80
Exposure Time 1/604
Image Size 4032x3024
GPS Position 21.016881 N, 101.256767 W
GPS Altitude 2014.6 m Above Sea Level
GPS Img Direction 104.9418297
Create Date 2023:01:05 11:05:21
View full resolution 猡达笌

Full resolution photos are available below the fold.

Note: I鈥檝e edited this post. It was first posted on January 4, 2023 with only the leading image. When I first posted the picture, it was in the early days of ChatGPT and I was of course excited to try to generate some text with it. I did so with a short description of Guanajuato City as a caption, and with the benefit of hindsight I鈥檓 no longer comfortable polluting the internet with content I didn鈥檛 write myself. All of the photos beyond the first were published on March 23, 2024 and the text is my own.

Pulling a Smartsheet table into Microsoft Excel using Power Query

Well if you thought my first post in eight months would be exotic, go ahead and smash that back button.

I use this technique when we have one-off assignments at work where I need a quick and dirty web-based data store that several people can collaborate on, and that can be easily queried in Excel without any intermediate infrastructure or processing. This would be quite trivial if not for Smartsheet鈥檚 intractible API format.

Assuming you have a Smartsheet grid you want to mirror in Excel and that can be refreshed on the fly, you鈥檒l need the sheet鈥檚 ID and an API bearer token for a user with viewer permissions.

In Excel, open Power Query and create a new query using the advanced editor. Make sure to replace $SHEET_ID and $BEARER_TOKEN. The query will bring in both your data and column headers.

let
    Source = Json.Document(
        Web.Contents("https://api.smartsheet.com/2.0/sheets/$SHEET_ID", [
            Headers=[
                #"Content-Type"="application/json",
                Authorization="Bearer $BEARER_TOKEN"
            ]
        ])
    ),

    // Process rows
    RowsData = Source[rows],
    RowsTable = Table.FromList(RowsData, Splitter.SplitByNothing()),
    ExpandedRows = Table.ExpandRecordColumn(
        RowsTable,
        "Column1",
        {"id", "rowNumber", "expanded", "createdAt", "modifiedAt", "cells", "siblingId"},
        {"ID", "RowNumber", "Expanded", "CreatedAt", "ModifiedAt", "Cells", "SiblingId"}
    ),
    ExpandCells = Table.ExpandListColumn(ExpandedRows, "Cells"),
    ExpandedCellsDetails = Table.ExpandRecordColumn(
        ExpandCells,
        "Cells",
        {"columnId", "value", "displayValue"},
        {"ColumnID", "CellValue", "CellDisplayValue"}
    ),
    RemovedCellsMetaColumns = Table.RemoveColumns(
        ExpandedCellsDetails,
        {"ID", "Expanded", "CreatedAt", "ModifiedAt", "CellDisplayValue", "SiblingId"}
    ),
    PivotedCellsByColumnId = Table.Pivot(
        Table.TransformColumnTypes(RemovedCellsMetaColumns, {{"ColumnID", type text}}),
        List.Distinct(Table.TransformColumnTypes(RemovedCellsMetaColumns, {{"ColumnID", type text}})[ColumnID]),
        "ColumnID",
        "CellValue"
    ),
    CleanRowData = Table.RemoveColumns(PivotedCellsByColumnId, {"RowNumber"}),

    // Process columns
    ColumnsData = Source[columns],
    ColumnsTable = Table.FromList(ColumnsData, Splitter.SplitByNothing()),
    ExpandedColumns = Table.ExpandRecordColumn(
        ColumnsTable,
        "Column1",
        {"id", "title"},
        {"ColumnID", "ColumnTitle"}
    ),
    ColumnTitlesMapped = Table.Pivot(
        Table.TransformColumnTypes(ExpandedColumns, {{"ColumnID", type text}}),
        List.Distinct(Table.TransformColumnTypes(ExpandedColumns, {{"ColumnID", type text}})[ColumnID]),
        "ColumnID",
        "ColumnTitle"
    ),

    // Add headers
    CombinedDataTable = Table.Combine({ColumnTitlesMapped, CleanRowData}),
    FinalData = Table.PromoteHeaders(CombinedDataTable)

in
    FinalData

How to checkout and edit a pull request locally

Let鈥檚 say you have a dependabot pull request and Charlie Marsh has added a new check to Ruff that causes your lint check to fail. You can fix the lint error and push the changes back to the pull request branch!

First, checkout the pull request locally:

# In this case, I'm updating ruff to v0.0.278
git fetch origin dependabot/pip/ruff-0.0.278
git switch --track origin/dependabot/pip/ruff-0.0.278

We鈥檝e now checked out the PR branch and set it to track the remote. We can use this pattern to keep tabs on long-running PRs, or as in this case, simply push an additional patch before merging. If you鈥檇 like a more friendly local branch name, you can append the :my-branch-name to the end of the git fetch call, and then call git switch my-branch-name to check it out; just keep in mind that this won鈥檛 set the local branch to track the remote.

In my case, this ruff release does not provide any new rule categories and my lints still pass, however I鈥檇 like to update the ruff version in my .pre-commit-config.yaml file so that it鈥檚 consistent with my requirements.txt. I鈥檒l make that change, commit and push back to the remote.

git add .pre-commit-config.yaml
git commit -m "Update pre-commit config."
git push

At this point, your checks should fire again and you can merge using your preferred merge method into your trunk. Check out real pull request to see how this looks server side.

Running a local Kubernetes cluster with Kind: A step-by-step guide

It has happened. I thought I could avoid it, but here we are. As if getting your program to run on one computer wasn鈥檛 hard enough, now we have to run it on multiple computers at the same time? They have played us for absolute fools.

Anyway, assuming we have some shared experience with Docker, let鈥檚 introduce some terminology:

  • A Pod is the smallest deployable unit in Kubernetes, often a single instance of an application. As I understand it, a pod is the logical equivalent of a container.
  • Nodes are the machines that host these pods. More nodes allow for more redundancy.
  • A Cluster is a set of nodes with the same job. A cluster can run multiple nodes, and a node can run multiple pods, and a pod typically consists of between two and fifteen orca whales.
  • A Service is an abstraction which provides a single network entry point to distribute traffic across the cluster.

For local development I am using Kind, a tool which allows you to run Kubernetes clusters in Docker containers. It is a lightweight way to run docker containers inside kubernetes inside a docker container (pause for effect).

The command to create a cluster is: kind create cluster

To deploy the application, it needs to be packaged as a Docker image. After creating the Dockerfile, the image is built and loaded into the Kind cluster with the following commands:

docker build -t my-image-name .

kind load docker-image my-image-name

I should note that in addition to Kind, there is a tool called minikube which is similar, though it requires you to set up a container registry.

The next step is creating a deployment and a service for the application by creating kubernetes manifest files in your project directory. The simplest possible configuration is something like so:

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-image-name-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-image-name
  template:
    metadata:
      labels:
        app: my-image-name
    spec:
      containers:
        - name: my-image-name
          image: my-image-name
          imagePullPolicy: Never # Use for local image
          ports:
            - containerPort: 8000 # Use the port your application runs on

Note that the imagePullPolicy is set to Never because we are using a local image with the implied tag latest. Specifying a specific tag should make this unnecessary, otherwise the default behaviour is to try to pull the image from Docker Hub, which will fail each time (or worse, deploy something unexpected).

In addition to matching the exposed port of the container, your application should be configured to bind to any incoming address (0.0.0.0), not just localhost.

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-image-name-service
spec:
  type: NodePort
  ports:
    - port: 8000
      nodePort: 30080
  selector:
    app: my-image-name

With these files in place, we can create the deployment and service respectively using kubectl apply -f <file-name> for each. They can be verified using: kubectl get deployments and kubectl get services.

If there are any issues, logs can be checked using: kubectl logs <pod-name>, and the pod name can be found using kubectl get pods.

Remember to specify environment variables in the deployment.yaml file under env in the containers specification if your application requires them.

If you鈥檙e running docker inside a linux virtual machine, port 30080 should already be exposed. If you鈥檙e running using Docker Desktop, there鈥檚 one more step which requires forwarding a local port to the service port. This can be done using:

kubectl port-forward service/my-image-name-service 30080:8000

This will map the service to localhost:30080 on your local machine. Launch it in tmux or append the command with an ampersand as it will block the terminal otherwise.

Fin. Now deploy to prod on a Friday afternoon and you鈥檙e done!