<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Jonas&#x27; Blog</title>
    <subtitle>No idea where this is going</subtitle>
    <link href="https://fassbender.dev/blog/atom.xml" rel="self" type="application/atom+xml"/>
    <link href="https://fassbender.dev/blog"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2023-07-28T15:00:00+00:00</updated>
    <id>https://fassbender.dev/blog/atom.xml</id>
    <entry xml:lang="en">
        <title>How to Extract a Collection from a Mongodump Archive</title>
        <published>2023-07-28T15:00:00+00:00</published>
        <updated>2023-07-28T15:00:00+00:00</updated>
        <author>
          <name>Jonas Fassbender</name>
        </author>
        <link rel="alternate" href="https://fassbender.dev/blog/003-mongodump-archive-to-json/" type="text/html"/>
        <id>https://fassbender.dev/blog/003-mongodump-archive-to-json/</id>
        <content type="html">&lt;p&gt;Since I started using MongoDB two years ago as my database of choice
when building web services, it has been a pleasure.
Everything has been easy and there was never a fuzz.
The &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;mongodb&quot;&gt;Rust client&lt;&#x2F;a&gt; is amazing and building 
RESTful web services in Rust with MongoDB for persistent storage has taken me a 
long way.
Only for full-text search I had to bring in a database that is more
advanced in that regard (Elasticsearch).
Besides the great integration into the Rust ecosystem, another positive aspect 
is the tooling and scripting capabilities.
The &lt;a href=&quot;https:&#x2F;&#x2F;www.mongodb.com&#x2F;docs&#x2F;mongodb-shell&#x2F;&quot;&gt;&lt;code&gt;mongosh&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; REPL for 
JavaScript is a versatile tool making database administration, document updates
and data retrieval for one-off jobs and analytics tasks a breeze.
Another useful set of developer tools from the MongoDB world are
the &lt;a href=&quot;https:&#x2F;&#x2F;www.mongodb.com&#x2F;docs&#x2F;database-tools&#x2F;&quot;&gt;database tools&lt;&#x2F;a&gt;.
The MongoDB database tools are a set of cli programs you can use to interact
with your deployment.
In this article I will show you how you can use the data import and export tools, 
namely &lt;a href=&quot;https:&#x2F;&#x2F;www.mongodb.com&#x2F;docs&#x2F;database-tools&#x2F;mongodump&#x2F;&quot;&gt;&lt;code&gt;mongodump&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;,
&lt;a href=&quot;https:&#x2F;&#x2F;www.mongodb.com&#x2F;docs&#x2F;database-tools&#x2F;mongorestore&#x2F;&quot;&gt;&lt;code&gt;mongorestore&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and 
&lt;a href=&quot;https:&#x2F;&#x2F;www.mongodb.com&#x2F;docs&#x2F;database-tools&#x2F;mongoexport&#x2F;&quot;&gt;&lt;code&gt;mongoexport&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, to 
extract a collection from a &lt;code&gt;mongodump&lt;&#x2F;code&gt; archive file.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why&quot; aria-label=&quot;Anchor link for: why&quot;&gt;#&lt;&#x2F;a&gt;
Why?&lt;&#x2F;h1&gt;
&lt;p&gt;Good question.
First though, what is a &lt;code&gt;mongodump&lt;&#x2F;code&gt; archive and why do I have a lot of them 
on various hard drives?
&lt;code&gt;mongodump&lt;&#x2F;code&gt; is a utility that creates a binary dump of your MongoDB deployment,
no matter whether the deployment is a standalone instance, replica set or 
sharded cluster.
You provide &lt;code&gt;mongodump&lt;&#x2F;code&gt; with a connection string to the deployment and it will 
dump every database with every collection in a binary format 
(&lt;a href=&quot;https:&#x2F;&#x2F;www.mongodb.com&#x2F;basics&#x2F;bson&quot;&gt;Bson&lt;&#x2F;a&gt;) to your local disk.
Each collection will be dumped to its own Bson file, along with a Json file
containing metadata about the collection.
Let&#x27;s say you have a MongoDB server running locally with a single database
called &lt;code&gt;my_database&lt;&#x2F;code&gt; containing the collections &lt;code&gt;my_collection_1&lt;&#x2F;code&gt; and 
&lt;code&gt;my_collection_2&lt;&#x2F;code&gt;.
When running &lt;code&gt;mongodbump&lt;&#x2F;code&gt; without any arguments, it will create a sub-directory 
&lt;code&gt;dump&#x2F;&lt;&#x2F;code&gt; in your current work directory with the following files:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;.
&lt;&#x2F;span&gt;&lt;span&gt;├── admin
&lt;&#x2F;span&gt;&lt;span&gt;│   ├── system.version.bson
&lt;&#x2F;span&gt;&lt;span&gt;│   └── system.version.metadata.json
&lt;&#x2F;span&gt;&lt;span&gt;└── my_database
&lt;&#x2F;span&gt;&lt;span&gt;    ├── my_collection_1.bson
&lt;&#x2F;span&gt;&lt;span&gt;    ├── my_collection_1.metadata.json
&lt;&#x2F;span&gt;&lt;span&gt;    ├── my_collection_2.bson
&lt;&#x2F;span&gt;&lt;span&gt;    └── my_collection_2.metadata.json
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Nice. We have a bunch of files containing the documents of our collections
saved locally on disk.
This is very helpful for creating backups of our production database.
But &lt;code&gt;mongodump&lt;&#x2F;code&gt; can do even better.
Instead of creating a directory, it can also create an archive file, making
the backup more self-contained and space efficient.
Running &lt;code&gt;mongodump --archive=foo.dump&lt;&#x2F;code&gt; will create the &lt;code&gt;foo.dump&lt;&#x2F;code&gt; archive
with the files from the listing above.
Perfect. My backup strategy for my MongoDB servers.&lt;&#x2F;p&gt;
&lt;p&gt;Why do I need offline access to the data stored in the backups?
In my case I need offline access, because the service storing the data got 
retired and does not exist anymore.
Without an immediate successor system, the data is no longer available online.
The only place the data can be retrieved from are the backup archives.
Even though the service got retired, the data still needs to be accessible 
for various (manual) operations beyond the original reason for collection.
This includes operations like user support, analytics, regulatory ones 
(&lt;a href=&quot;https:&#x2F;&#x2F;gdpr-info.eu&#x2F;art-15-gdpr&#x2F;&quot;&gt;right of access&lt;&#x2F;a&gt;) and maybe one day 
migration to a new system.
The backups themselves are stored in a binary format.
Their only purpose is to be machine-interpretable in order to restore the
contents to a MongoDB deployment.
But to fulfill the ongoing business needs, the data must be accessed by people 
and therefore needs to be human-readable.
So we need to extract it from the archive somehow.&lt;&#x2F;p&gt;

    
    
    
    


&lt;div class=&quot;bg-admonition-note-light dark:bg-admonition-note-dark border-solid border-0 border-l-4 border-admonition-note-border shadow-sm rounded-md p-4 pb-1 my-4&quot;&gt;
    &lt;div class=&quot;uppercase font-bold text-lg&quot;&gt;
        &lt;i class=&quot;fas fa-circle-info pr-1&quot;&gt;&lt;&#x2F;i&gt;
        note
    &lt;&#x2F;div&gt;
    &lt;div&gt;
        &lt;p&gt;Before retiring the service I thought long and hard about how I intent to 
retain access to the data offline.
I did not delete the service only to realize later that I lost necessary business 
information, scrambling to restore it, coming up with the solution 
presented here.
Instead of the obvious other way of getting offline access through an extra dump 
of the data in a human-readable format like Json or CSV, I thought that I 
must be able to get the data from the backups instead.
I deemed retrieving the data from the backups preferable, mainly for two 
reasons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Replication:&lt;&#x2F;em&gt; Backups were made daily and twice a week the backup device was 
changed.
Every hard drive not currently connected to the server is stored in a secure 
location off-site. 
Before shutting down the service completely, there was a grace period where 
write access was revoked and people could only read the existing data.
During that grace period the final, immutable state of the data was replicated 
onto every backup hard drive.
Getting the same replication for an extra dump seemed like a lot of unnecessary
manual work, requiring me to get every hard drive, drive to my server&#x27;s 
location and one by one plug them in and copy the final dump to every drive.
Another possibility would&#x27;ve been to change the backup routine to dump 
human-readable data and let it run for another three weeks to populate every 
device with at least one copy of the data.
But that would&#x27;ve meant unnecessarily paying for the hosting of a useless 
shell of a service.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;History:&lt;&#x2F;em&gt;  All the backups together make up a coarsely grained history of 
the data and the changes that were made to it.
If it turns out that a document is broken in the final data dump and we need 
to find the correct version somewhere in the older backups, I&#x27;d need to be 
able to sift through the backups anyway.
Might as well write the script for it now when I can use it for other
operations that require offline access and kill two (one being hypothetical) 
birds with one stone.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;

    &lt;&#x2F;div&gt;
&lt;&#x2F;div&gt;

    
    
    
    


&lt;div class=&quot;bg-admonition-caution-light dark:bg-admonition-caution-dark border-solid border-0 border-l-4 border-admonition-caution-border shadow-sm rounded-md p-4 pb-1 my-4&quot;&gt;
    &lt;div class=&quot;uppercase font-bold text-lg&quot;&gt;
        &lt;i class=&quot;fas fa-exclamation-triangle pr-1&quot;&gt;&lt;&#x2F;i&gt;
        caution
    &lt;&#x2F;div&gt;
    &lt;div&gt;
        &lt;p&gt;Note that while this backup strategy is good enough for the scale I operate
at, it might not be suitable for your use-case.
&lt;code&gt;mongodump&lt;&#x2F;code&gt;and &lt;code&gt;mongorestore&lt;&#x2F;code&gt; are not meant to back up larger MongoDB deployments.
While &lt;code&gt;mongdump&lt;&#x2F;code&gt; can connect to sharded clusters and create binary data dumps,
it should not be used to create backups, as &lt;code&gt;mongodump&lt;&#x2F;code&gt; does not maintain the
atomicity guarantees of transactions across shards.
Read more about it 
&lt;a href=&quot;https:&#x2F;&#x2F;www.mongodb.com&#x2F;docs&#x2F;manual&#x2F;core&#x2F;backups&#x2F;#std-label-backup-with-mongodump&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;

    &lt;&#x2F;div&gt;
&lt;&#x2F;div&gt;
&lt;h1 id=&quot;required-steps&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#required-steps&quot; aria-label=&quot;Anchor link for: required-steps&quot;&gt;#&lt;&#x2F;a&gt;
Required Steps&lt;&#x2F;h1&gt;
&lt;p&gt;I was hoping to be able to extract the data from the archive file without
spinning up a local MongoDB instance.
In my mind this sounded like too much overhead for such a benign task. 
But it turns out my use-case described above is less common than anticipated.
I thought that I must be able to use &lt;code&gt;mongodump&lt;&#x2F;code&gt; itself or at least its 
counterpart &lt;code&gt;mongorestore&lt;&#x2F;code&gt; to query the data from an archive file and print the 
documents in a human-readable format to a file or stdout.
Looking through the documentation quickly revealed that this is not the case.
The MongoDB database tools are split between binary and text import&#x2F;export 
tools.
On the binary side you have &lt;code&gt;mongodump&lt;&#x2F;code&gt; and &lt;code&gt;mongorestore&lt;&#x2F;code&gt; with the equivalent 
tools &lt;code&gt;mongoexport&lt;&#x2F;code&gt; and &lt;code&gt;mongoimport&lt;&#x2F;code&gt; for working with the text formats Json,
CSV and TSV.
Even though it turns out that we can&#x27;t use only the binary tools to retrieve the 
data from an archive in text format, condemning such a clean and consistent 
design would be preposterous.&lt;&#x2F;p&gt;
&lt;p&gt;There is a third CLI tool for working with binary data dumps in the MongoDB 
database tools, &lt;code&gt;bsondump&lt;&#x2F;code&gt;.
&lt;code&gt;bsondump&lt;&#x2F;code&gt; is able to convert Bson files to Json, which sounds very much like 
what we are looking for.
Unfortunately &lt;code&gt;bsondump&lt;&#x2F;code&gt; is only able to work with uncompressed Bson files.
We don&#x27;t have these, as we are writing our dumps to an archive instead of the
file system directly.
&lt;code&gt;bsondump&lt;&#x2F;code&gt; is not able to work with the archives, a feature exclusive to 
&lt;code&gt;mongodump&lt;&#x2F;code&gt; and &lt;code&gt;mongorestore&lt;&#x2F;code&gt;.
Last thing I checked was whether it is possible to extract the files from the
archive using an available decompression program.
It is not an uncommon pattern to base domain-specific archive formats on 
general-purpose formats like Tar or Zip.
For example, &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;cargo&#x2F;commands&#x2F;cargo-package.html&quot;&gt;&lt;code&gt;cargo package&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
creates simple Tarballs from Cargo packages while Java&#x27;s &lt;code&gt;.jar&lt;&#x2F;code&gt; files are
just Zip archives with a metadata file in a specific location within the 
archive.
Unfortunately this is &lt;a href=&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;a&#x2F;56519349&#x2F;20665825&quot;&gt;not true&lt;&#x2F;a&gt; 
for the archives created by &lt;code&gt;mongodump&lt;&#x2F;code&gt;.
So extracting the binary file containing the documents from a collection and
subsequently using &lt;code&gt;bsondump&lt;&#x2F;code&gt; to read the file was not possible either.
This means the only way we can extract the data in a human-readable format from
an archive is by starting a MongoDB instance, restoring the archive by running
&lt;code&gt;mongorestore --archive=foo.dump&lt;&#x2F;code&gt; and lastly downloading the data again with
&lt;code&gt;mongoexport&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;docker-and-bash-to-the-rescue&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#docker-and-bash-to-the-rescue&quot; aria-label=&quot;Anchor link for: docker-and-bash-to-the-rescue&quot;&gt;#&lt;&#x2F;a&gt;
Docker and Bash to the Rescue&lt;&#x2F;h1&gt;
&lt;p&gt;What sounded like another overly complex Ops process turned out to be nothing
more than a simple and idempotent Bash script, thanks to Docker and the 
&lt;a href=&quot;https:&#x2F;&#x2F;hub.docker.com&#x2F;_&#x2F;mongo&quot;&gt;&lt;code&gt;mongo&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; image.
The necessary steps are:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Create a container from the &lt;code&gt;mongo&lt;&#x2F;code&gt; image&lt;&#x2F;li&gt;
&lt;li&gt;Copy the archive file to the container &lt;&#x2F;li&gt;
&lt;li&gt;Restore it to the MongoDB instance running inside the container&lt;&#x2F;li&gt;
&lt;li&gt;Export the collection we want to extract to a file &lt;&#x2F;li&gt;
&lt;li&gt;Copy the file from the container to the local machine&lt;&#x2F;li&gt;
&lt;li&gt;Delete the container again &lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;em&gt;Et voilà&lt;&#x2F;em&gt;, we extracted a collection from our binary archive into a 
human-readable format.
Without further ado, here is the Bash script:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;#!&#x2F;bin&#x2F;bash
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# CLI arguments 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ARCHIVE&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;DB&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;2
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;COLLECTION&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;3
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;OUT&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;4
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# spin up container
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; run&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -d --name&lt;&#x2F;span&gt;&lt;span&gt; mongodump_to_json mongo:latest
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# copy archive into container
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; cp $&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ARCHIVE&lt;&#x2F;span&gt;&lt;span&gt; mongodump_to_json:archive.dump
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# extract archive
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; exec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -t&lt;&#x2F;span&gt;&lt;span&gt; mongodump_to_json mongorestore&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; --archive&lt;&#x2F;span&gt;&lt;span&gt;=archive.dump
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# export collection to json file
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; exec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -t&lt;&#x2F;span&gt;&lt;span&gt; mongodump_to_json mongoexport&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; --pretty --db&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;DB --collection&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;COLLECTION -o&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;OUT
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; cp mongodump_to_json:$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;OUT &lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;OUT
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# gracefully shut down container again
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; stop mongodump_to_json
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; rm mongodump_to_json
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;All we need to do is provide four arguments to the script.
The path to the archive file we want to use, which collection from which 
database we want to retrieve and finally where to store the extracted data.
If we wanted to retrieve &lt;code&gt;my_collection_2&lt;&#x2F;code&gt; from the &lt;code&gt;foo.dump&lt;&#x2F;code&gt; archive from the 
example above, we&#x27;d call the script like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sh&lt;&#x2F;span&gt;&lt;span&gt; mongodump_to_json.sh foo.dump my_database my_collection_2 my_collection_2.json
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This command creates a &lt;code&gt;my_collection_2.json&lt;&#x2F;code&gt; file in the current work directory 
on the host machine with the documents of &lt;code&gt;my_collection_2&lt;&#x2F;code&gt; formatted as a 
Json array.
Human-readable and actionable, ready to be put to use.&lt;&#x2F;p&gt;
&lt;p&gt;While I was hoping to accomplish extracting a collection from a &lt;code&gt;mongodump&lt;&#x2F;code&gt; 
archive file without first recreating the collection in a local MongoDB instance
before downloading it again in a different format, it turned out that the 
database tools were not meant for that.
&lt;code&gt;bsondump&lt;&#x2F;code&gt; exists, but it is unable to work with archive files, only with
standalone Bson files.
But thanks to Docker and the &lt;code&gt;mongo&lt;&#x2F;code&gt; container, it is not necessary to create
a complex local MongoDB installation.
Just a simple, self-contained and most importantly idempotent Bash script that
only calls various Docker commands under the hood is needed to do the job and 
I&#x27;m happy with the solution.&lt;&#x2F;p&gt;

    
    
    
    


&lt;div class=&quot;bg-admonition-note-light dark:bg-admonition-note-dark border-solid border-0 border-l-4 border-admonition-note-border shadow-sm rounded-md p-4 pb-1 my-4&quot;&gt;
    &lt;div class=&quot;uppercase font-bold text-lg&quot;&gt;
        &lt;i class=&quot;fas fa-circle-info pr-1&quot;&gt;&lt;&#x2F;i&gt;
        note
    &lt;&#x2F;div&gt;
    &lt;div&gt;
        &lt;p&gt;The script creates a container from the latest version of the &lt;code&gt;mongo&lt;&#x2F;code&gt; image.
As of July 2023, if your production system is running MongoDB version 4.0 or 
earlier, you may run into compatibility issues when using the database tools 
installed in the image. 
If that&#x27;s the case you should use an earlier version of the &lt;code&gt;mongo&lt;&#x2F;code&gt; image and
try again.
See i.e. &lt;a href=&quot;https:&#x2F;&#x2F;www.mongodb.com&#x2F;docs&#x2F;database-tools&#x2F;mongodump&#x2F;#mongodb-server-compatibility&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;

    &lt;&#x2F;div&gt;
&lt;&#x2F;div&gt;
&lt;h1 id=&quot;optional-slap-on-a-rudimentary-cli&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#optional-slap-on-a-rudimentary-cli&quot; aria-label=&quot;Anchor link for: optional-slap-on-a-rudimentary-cli&quot;&gt;#&lt;&#x2F;a&gt;
Optional: Slap On a Rudimentary CLI&lt;&#x2F;h1&gt;
&lt;p&gt;To hide the fact that deep down I&#x27;m a thoughtless savage, I like to keep up 
the pretense of being a sane and stable person by adding a rudimentary CLI to 
my Bash scripts if they need to be executed with arguments.
I&#x27;m not going as far as writing a help or man page (Rust&#x27;s 
&lt;a href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;clap&#x2F;latest&#x2F;clap&#x2F;&quot;&gt;&lt;code&gt;clap&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; has ruined CLI programming for me 
with its incredible API for creating interfaces with zero effort). 
But being able to provide named arguments whose existence is checked before 
anything weird can happen, or providing sensible default values for optional 
arguments gives me a better feeling about the whole script.
Here&#x27;s the final version of the script I use, with the logic abstracted into a 
function and a crude little CLI added on top: &lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;#!&#x2F;bin&#x2F;bash
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;mongodump_to_json&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ARCHIVE&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;1
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;DB&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;2
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;COLLECTION&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;3
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;OUT&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;4
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# spin up container
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; run&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -d --name&lt;&#x2F;span&gt;&lt;span&gt; mongodump_to_json mongo:latest
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# copy archive into container
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; cp $&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ARCHIVE&lt;&#x2F;span&gt;&lt;span&gt; mongodump_to_json:archive.dump
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# extract archive
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; exec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -t&lt;&#x2F;span&gt;&lt;span&gt; mongodump_to_json mongorestore&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; --archive&lt;&#x2F;span&gt;&lt;span&gt;=archive.dump
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# export collection to json file
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; exec&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -t&lt;&#x2F;span&gt;&lt;span&gt; mongodump_to_json mongoexport&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; --pretty --db&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;DB --collection&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;COLLECTION -o&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;OUT
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; cp mongodump_to_json:$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;OUT &lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;OUT
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# gracefully shut down container again
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; stop mongodump_to_json
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt; rm mongodump_to_json
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;while &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;[[ &lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;# -gt&lt;&#x2F;span&gt;&lt;span&gt; 0 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;]]&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;do
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;case &lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;1 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;in
&lt;&#x2F;span&gt;&lt;span&gt;    -a|--archive&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ARCHIVE&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;2
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;shift
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;shift
&lt;&#x2F;span&gt;&lt;span&gt;      ;;
&lt;&#x2F;span&gt;&lt;span&gt;    --db&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;DB&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;2
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;shift
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;shift
&lt;&#x2F;span&gt;&lt;span&gt;      ;;
&lt;&#x2F;span&gt;&lt;span&gt;    --collection&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;COLLECTION&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;2
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;shift
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;shift
&lt;&#x2F;span&gt;&lt;span&gt;      ;;
&lt;&#x2F;span&gt;&lt;span&gt;    -o|--out&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;OUT&lt;&#x2F;span&gt;&lt;span&gt;=$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;2
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;shift
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;shift
&lt;&#x2F;span&gt;&lt;span&gt;      ;;
&lt;&#x2F;span&gt;&lt;span&gt;    *&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;echo &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;UNKNOWN ARGUMENT: &lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;exit&lt;&#x2F;span&gt;&lt;span&gt; 1
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;esac
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;done
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# required arguments
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;[ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-z &lt;&#x2F;span&gt;&lt;span&gt;${&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ARCHIVE&lt;&#x2F;span&gt;&lt;span&gt;+x} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;then
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;echo &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;MISSING ARGUMENT: -a&#x2F;--archive&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;exit&lt;&#x2F;span&gt;&lt;span&gt; 1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fi
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;[ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-z &lt;&#x2F;span&gt;&lt;span&gt;${&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;DB&lt;&#x2F;span&gt;&lt;span&gt;+x} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;then
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;echo &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;MISSING ARGUMENT: --db&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;exit&lt;&#x2F;span&gt;&lt;span&gt; 1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fi
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;[ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-z &lt;&#x2F;span&gt;&lt;span&gt;${&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;COLLECTION&lt;&#x2F;span&gt;&lt;span&gt;+x} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;then
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;echo &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;MISSING ARGUMENT: --collection&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;exit&lt;&#x2F;span&gt;&lt;span&gt; 1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fi
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# optional arguments
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;[ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-z &lt;&#x2F;span&gt;&lt;span&gt;${&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;OUT&lt;&#x2F;span&gt;&lt;span&gt;+x} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;then
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;OUT&lt;&#x2F;span&gt;&lt;span&gt;=&amp;quot;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;DB&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;COLLECTION&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;.json&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fi
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mongodump_to_json &lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ARCHIVE &lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;DB &lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;COLLECTION &lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;OUT
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The interface is now more flexible and harder to misuse.
Executing the script looks a lot nicer and less random than before as well:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sh&lt;&#x2F;span&gt;&lt;span&gt; mongodump_to_json.sh&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -a&lt;&#x2F;span&gt;&lt;span&gt; foo.dump&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; --db&lt;&#x2F;span&gt;&lt;span&gt; my_database&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; --collection&lt;&#x2F;span&gt;&lt;span&gt; my_collection_2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -o&lt;&#x2F;span&gt;&lt;span&gt; my_collection_2.json
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Not a proper CLI ready for major distribution, but good enough for myself.&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Going Running for the First Time</title>
        <published>2023-07-10T15:00:00+00:00</published>
        <updated>2023-07-10T15:00:00+00:00</updated>
        <author>
          <name>Jonas Fassbender</name>
        </author>
        <link rel="alternate" href="https://fassbender.dev/blog/002-first-time-running/" type="text/html"/>
        <id>https://fassbender.dev/blog/002-first-time-running/</id>
        <content type="html">&lt;p&gt;A couple of months ago I went running for the first time.
Not for the first time ever, but for the first time in a really long time.
So long that it warrants a slightly more sensational title.
I think the last time I went for a run—it was so long ago that I can&#x27;t
remember exactly when—was in the early summer months of 2015, pretty 
much exactly eight years ago.
I never enjoyed running when I did it more or less regularly back then.
In order to become a better athlete, I did it anyway.
As I&#x27;m getting older while trying to be a healthy and happy individual, I 
decided to give running another chance.
But my first try didn&#x27;t go as planned and ended up being a completely ludicrous 
and painful endeavour.
Although we were born to run [&lt;a href=&quot;https:&#x2F;&#x2F;fassbender.dev&#x2F;blog&#x2F;002-first-time-running&#x2F;#ref-1&quot;&gt;1&lt;&#x2F;a&gt;], the act of rapidly putting one 
feet in front of the other can be abused until it becomes painful and 
destructive.
To pervert something so ordinary, all you need is a morbid wish for 
penance through physical exercise, a tendency for absurd overestimation of your 
own skills and the inability to give up on unrealistic goals.&lt;&#x2F;p&gt;
&lt;p&gt;I make it sound like the first run after my prolonged absence was a marathon or 
some other &lt;em&gt;über&lt;&#x2F;em&gt;-human distance.
Unfortunately it was not.
I don&#x27;t need to run 200 miles in two days straight like Courtney Dauwalter to 
enter my pain cave. 
Just eight kilometers in about an hour, never far from home are 
sufficient (one of the most uncomfortable ailments afterwards was my hurting 
ego).
Before it all, I thought I was in peak physical condition.
I&#x27;ve been doing sports pretty much my whole life, with only two notable 
absences.
One year when I was fourteen and three years from 2017 to 2020.
The former was due to inflammation in my knee (I got diagnosed with 
Osgood-Schlatter disease) and the latter was caused by pure and utter 
laziness.
Before 2017 I was playing basketball in a club and all other athletic 
training—including running—was accessory to my goal of 
becoming a good basketball player. 
A few things happened in my life back then, one being the development of chronic 
pain in my knees, shins and feet due to flat-footedness.
I stopped playing basketball, a sport I&#x27;ve played since I remember.
Doing some exercises in the months that followed, I never mustered enough 
motivation to stick to a training schedule without basketball.
The next three years I did not exercise regularly, no weightlifting and for 
sure no running.
I lost some weight but otherwise stayed in decent enough shape.&lt;&#x2F;p&gt;
&lt;p&gt;In 2020 then, a friend—a decent amateur strength athlete—and 
I were discussing the fact that we were not using the offerings of our 
university enough.
He suggested to go to the gym together, as our uni had its own (the 
Pleasance Gym in Edinburgh, terrific place) with reasonable plans and fair 
prices for enrolled students.
I politely declined.
He was adamant and told me, we could go and play a game of squash.
I yielded and we went to the gym together.
He tricked me.
Sure, we went to the Pleasance to play squash.
But to my surprise we arrived thirty minutes early.&lt;&#x2F;p&gt;
&lt;p&gt;&amp;quot;What should we do until our court is ready?&amp;quot; he asked.&lt;&#x2F;p&gt;
&lt;p&gt;&amp;quot;I don&#x27;t know.&amp;quot;&lt;&#x2F;p&gt;
&lt;p&gt;&amp;quot;Well, we could go down to the vaults and warm up.&amp;quot;&lt;&#x2F;p&gt;
&lt;p&gt;&amp;quot;Alright.&amp;quot; I answered innocently and followed like the lamb to the slaughter.&lt;&#x2F;p&gt;
&lt;p&gt;We did squats. 
For warm-ups. 
Squatting after three years with no weight training was nearly as painful as 
going running after an eight year hiatus. 
The soreness was excruciating and lasted a whole week.
One of the best parts of my daily routine was walking to and from uni through 
beautiful Edinburgh.
From Leith along Leith Walk to bustling Princes Street, across North Bridge 
with its breathtaking view over epic Old and New Town, Scott Monument and the 
noble Balmoral and finally alongside Old College through Potterrow Port to the 
Bayes Centre and back.
2.5 kilometers one-way.
I hobbled the whole distance, but it was absolutely worth it.
I was hooked on weightlifting again.
The stress relief and your mind feeling light and fluffy from the colourful 
endorphins are just too good.
Combined with the reward of getting stronger, feeling healthier and a more 
attractive physical appearance, I was back on the grind and have been since 
that day I completely wracked my legs and lower back.
It has been almost exclusively weightlifting for me since then, with a few 
bodyweight exercises and the occasional squash match interspersed. &lt;&#x2F;p&gt;
&lt;p&gt;A couple of months ago then, another friend &lt;em&gt;L&lt;&#x2F;em&gt; stayed over for a few weeks.
He&#x27;s a decent amateur endurance athlete and asked me if I&#x27;d like to join him on 
one of his runs.
I politely declined.
He was adamant and told me, I&#x27;d feel great afterwards.
I yielded and we went running together.
If there is a morale to this tale then it&#x27;s probably that you shouldn&#x27;t give in 
to your silly friends so easily.
While spinelessness plays a prominent role in my every-day decision making, it 
was not the only reason I took him up on his offer.
I&#x27;ve been contemplating going running again, because only with weightlifting 
I&#x27;ve been unable to address two of my most glaring weaknesses: an underdeveloped 
cardiovascular system and weak-ass flatfeet.
Both scream for moderate running with focus on proper forefoot striking 
[&lt;a href=&quot;https:&#x2F;&#x2F;fassbender.dev&#x2F;blog&#x2F;002-first-time-running&#x2F;#ref-2&quot;&gt;2&lt;&#x2F;a&gt;,&lt;a href=&quot;https:&#x2F;&#x2F;fassbender.dev&#x2F;blog&#x2F;002-first-time-running&#x2F;#ref-4&quot;&gt;4&lt;&#x2F;a&gt;].
However, the most important condition to set me in the right mood for running was 
the anxiety I was experiencing at the time.
Having a guest around, I wasn&#x27;t paying enough attention to my personal 
well-being, not following my carefully laid-out self-care routines, including
regular exercising.
The excitement of being the host to a dear friend turned sour.
Whenever I&#x27;m in such an agitated and restless state of mind, my subconscious 
turns to escapism manifesting itself in a wish for self-castigation through
excessive bodily exercise.
It feels like an itch that must be scratched till the skin is raw and tender. 
Or a demon that must be exorcised, if you so will.
Definitely a fine condition to be in before dusting off running shoes unused 
for nearly a decade.&lt;&#x2F;p&gt;
&lt;p&gt;The day I broke my eight year streak of no running finally arrived.
Throughout most of the day, I didn&#x27;t know what was going to happen that late 
afternoon.
Though the pressure inside my brain was high at the time, in the end the 
decision felt spontaneous.
It was just &lt;em&gt;L&lt;&#x2F;em&gt; telling me that he&#x27;d go for a run now, asking if I&#x27;d like to join 
and I said yes, without a second thought.
I knew I couldn&#x27;t keep up with him, so we decided to start together and split up.
I already had a route in mind.
I wanted to run to an outdoor gym I had seen when driving through a nearby
village.
My intention was to run there, do some pull ups and dips and run back.
After a couple of small starting problems (I took some time getting ready and 
had to turn around after a few meters and leave my mobile behind—it was too 
annoying to carry around with me), we finally hit the road.
It was a fair day with the sun hanging fat and bright in a lazy blue sky.
A gentle breeze made the warm air pleasant on the skin.
We started out running down a hill with a smooth and easy decline, allowing us
to take in the gorgeous view.
The hill lead down to a calm and civilized plain stretching far and wide,
full of brown fields of rapeseed and wheat.
Behind the plain wild and wooded foothills rose abruptly, overshadowed by
the taller mountains farther away, kings and queens with their crowns of snow.
I was thoroughly enjoying myself and the beauty all around me, but not for long.
Once we reached the plain I was already starting to struggle, my condition 
deteriorating quicker than I had anticipated. 
I was winded and my calves and feet felt like they were about to start cramping.
But the weakest link was my stomach.
Not knowing the day was about to get a lot more strenuous, I had a tremendous
and improper lunch.
We Germans love our bread and a meal consisting of a few slices with a bunch of 
savory toppings to make open sandwiches is something common for us.
It is certainly the meal I consumed the most in my live and I deeply value 
the nourishment it provides.
We had a nice and prolonged lunch with lots of thick slices of &lt;em&gt;Schwarzbrot&lt;&#x2F;em&gt;, 
topped with a thick layer of strong allioli, thick slices of 
fresh tomatoes and even thicker pieces of hearty cheese, made perfect with a 
drizzle of olive oil and arugula on top.
Some pickled olives and cucumbers and a small salad with a honey-mustard 
dressing on the side, it was pure ecstasy.
Although lunch was amazing, it was not the best choice of food to eat before 
taking my endurance to its limits and by the time we entered the plain,
I was feeling sicker with every step.&lt;&#x2F;p&gt;
&lt;p&gt;We finally caught sight of the crossroads where we would part.
A few trees and shrubs were giving valuable and scarce shade—in my 
unpreparedness, I had forgotten to put on sunscreen—and I 
decided this would be a fine place to have my first rest.
&lt;em&gt;L&lt;&#x2F;em&gt; was already distancing himself from me, until about 200 meters separated us 
when he reached the crossroads.
To add insult to injury, he turned around and came back to me.
When we reached the crossroads together this time, I told him he can go and run 
his route and I&#x27;ll run mine.
So he sprang off, taking a right.
I stayed behind in the shadow, bent over, gasping for air and absolution.
He ran around the corner, accelerating even more.
I retched.
He came prancing back, smiling and telling me he had taken the wrong way, 
he must take a right at the next crossing.
He took off to the left this time and disappeared around the other corner.
I retched again.
A mad and miserable creature standing next to the crossroads, sick to its 
stomach, feet and lungs burning.
But at the same time alive and free, purged of the anxiety and restlessness 
that had plagued me the last few days.
I had what I wanted.
No thoughts. 
The most brutal way of being present.&lt;&#x2F;p&gt;
&lt;p&gt;I didn&#x27;t knew it at the time but I already managed to run two kilometers,
two thirds of the usual distance I&#x27;d run back in my basketball days.
In my arrogance I didn&#x27;t bother to check the distance to the outdoor gym and 
badly miscalculated it.
Even with the self-destructive mindset and the overconfidence in my athletic 
abilities, I only aimed for five kilometers, a distance I had outrun maybe once 
or twice in my entire life.
I estimated running to the gym and back would cover that.
While waiting at the crossroads for the nausea to subside and for the devil to
show up and offer me the same deal he offered the great Robert Johnson and 
that fool Faust, I was contemplating giving up and returning home.
Entertaining that thought for a minute, I violently disregarded it.
Not reaching that cursed gym was not an option.
I retched a last time, stood up and trotted onwards.&lt;&#x2F;p&gt;
&lt;p&gt;By the time I continued, my feet and calves were cramping deliciously.
I managed to slowly stumble a few yards with forefoot striking but had to 
abandon it to relieve my aching muscles, switching to heel striking.
The switch of striking style allowed me to retain a rhythmic motion, but I 
knew it would be a temporary measure.
I don&#x27;t want to demonize heel striking and I&#x27;m certainly not trying to give
anyone any advice (this story should make it pretty clear that I&#x27;m in no 
position to do that), but I know from past experience that heel striking is not
the right style for me, probably due to improper form and structural reasons.
What is certain is that heel striking puts greater demand on the knees compared
to forefoot striking, which puts more demand on the Achilles tendon, ankle and 
foot muscles [&lt;a href=&quot;https:&#x2F;&#x2F;fassbender.dev&#x2F;blog&#x2F;002-first-time-running&#x2F;#ref-3&quot;&gt;3&lt;&#x2F;a&gt;].
Given my history with chronic knee pain, I&#x27;m careful not to put too much stress
on my knees to avoid reaggravation.&lt;&#x2F;p&gt;
&lt;p&gt;Running through brown and hot fields at a crawling pace, the only motivation I 
had left was the wish to reach another shady spot.
I didn&#x27;t want to add sunburn to my list of ailments.
It&#x27;s not pain that will make you stronger and grants relief.
It&#x27;s pain that will give you skin cancer.
I made my way to another crossroad with a couple of Mediterranean cypress trees,
luring me with their precious shade and sweet scent.
Resting beneath those ramrod straight and dusty soldiers unbowed by destiny or 
the memory of their ancient roots, my peace and lucidity was only disturbed by 
innocent civilians.
Their eyes and smiles showed their obliviousness to the magnitude of my heroic 
quest.
I had to continue my journey, rejuvenated by resignation and a desire to flee 
pending judgement.
The last section before reaching the God-forsaken outdoor gym was more of the 
same, the only difference being the last few hundred meters.
I completed those by walking through a cool and shady forest, only pretending to 
run once I was out of the woods and visible to other people again.
Vanity, definitely the devil&#x27;s favourite sin.&lt;&#x2F;p&gt;
&lt;p&gt;Finally I reached my destination.
What I found was nothing like I imagined. 
The outdoor gym I thought I had seen was not an outdoor gym at all.
To make the lunacy of the whole endeavour perfect, what had looked like an 
outdoor gym with bars and other fitness devices when I was driving by with my 
car, turned out to be some sort of weird adventure playground for children from 
years eight to fourteen.
It wasn&#x27;t like people older than fourteen were not allowed on that 
playground—I&#x27;ve gotten the idea that the playground was a gym from seeing 
old people working out there—but more that the devices 
are not suitable for taller folks.
What I had mistaken for a rack with an adjustable bar I could use to do pull-ups 
and dips was just a bar fixed in place at hip height.
Thoroughly enjoying the futility of what I had accomplished and the uselessness
of what I had discovered, I smiled and started my laborious way back.&lt;&#x2F;p&gt;
&lt;p&gt;On the return trip I was able to retain a steady stride suitable for my 
condition.
My knees were starting to get sore but still nothing compared to my burning 
feet and ankles.
I kept my pace easy and slow, stopping at the shady places I stopped before.
My brain was mercifully numb, shut down by the steady movement and exhaustion.
While I remember the disquiet during the first quarter of the run very well,
I can&#x27;t recall my state of mind while running back through the plane,
making me assume it was peaceful.
Or at least empty.
Until I reached the last section, back up the hill.
With my poor endurance I was immediately winded by the not-so-steep incline.
I was already running as slow as possible so I couldn&#x27;t decrease my speed any 
further or else I would&#x27;ve reverted to walking.
Luckily there was a shady spot at the end of the section with the steepest 
incline.
I used it to pause and recharge before the last stupidity I would commit that day.&lt;&#x2F;p&gt;
&lt;p&gt;Only a small hump and a hundred meters of flat homestretch was standing between 
me and the sweet relief of the end of this mess.
Cowering in the shade, staring down the last few meters of incline I had to
overcome, I went crazy.
It wasn&#x27;t the first or the most intense bout of short-term craziness I&#x27;ve
experienced due to fatigue and pain.
Once—in a similarly agitated state—I decided it&#x27;d be a proper 
blast to break-in new hiking boots with a fifty kilometer hike.
It turned my feet into a bloody pulp, but that&#x27;s a story for another day.
I witnessed an utterly irrational thought take roots in my mind,
conquer it and annihilate all reason I had left.
Completely filled with the conviction that the only way I&#x27;d manage the final 
stretch would be in a full-out sprint, that&#x27;s what I did.
Conceptually still able to grasp that this was a terrible idea and that this 
panting wreck of a body should not be pushed to run as fast as it still could, 
it didn&#x27;t matter.
Reason is worth nothing if you can be alive instead.
To make my first run after eight years perfect in its foolishness, I sprinted. 
Wobbling and staggering, moaning and groaning, wheezing and coughing, spitting 
and screaming I crossed the finish line.&lt;&#x2F;p&gt;
&lt;p&gt;How I felt afterwards nearly made up for the wreckage.
My first runner&#x27;s high.
Everything was soft and lovely.
When I finally sat down, an intense sense of calm washed over me, a feeling I 
had desperately needed.
At last, I was at peace.
Even the pain in my legs and lungs felt good.
A warm and tingly sensation started to arise underneath the cramps as my muscles 
started to relax.
I was happy and even when &lt;em&gt;L&lt;&#x2F;em&gt; returned shortly after—as flamboyant as ever 
and not exhausted at all from running twice the distance that nearly broke 
me—I couldn&#x27;t stop smiling.
My hurting ego, very much displeased with my performance, was unable to subdue 
my state of exuberance.
Alas, the feeling didn&#x27;t last.
After a prolonged shower the high was gone.
The calmness stayed a little longer, but without the high my body made me aware 
of what I had done.
While I expected discomfort in certain spots—most notably my feet, which 
did not disappoint with their exquisite soreness causing me a limp—there 
were unexpected parts of my organism feeling strange as well.
My immune system went into complete overdrive and my nose was stuffy all night.
I could also feel that my body temperature was higher than usual when I was lying 
in bed, like I had a slight fever.
Expecting to sleep like an angel after so much exhaustion, I slept badly, 
tossing and turning all night.
When I woke up the next day, another unforeseen part of my body had turned 
sore: my core.
While I thought I had gotten good core activation during my regular training 
routine, my abdomen was hurting with every breath. 
My good mood was gone the next morning as well.
While running helped to get rid of the surmounting everyday stress causing my 
troubled mind before, it was replaced with the stress from overdoing it.
It took me about three days to fully recover. 
On the eighth day I laced my running shoes again.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve been going running regularly since then.
Not such hellish runs like the first one.
It took me a little to calibrate, but I&#x27;ve found a distance and terrain 
suitable for my skill level.
Right now I run a stunning four kilometer route with eighty meters of 
altitude in about thirty-five minutes.
The track takes me through gorgeous and fragrant woods and along quaint villages 
with picturesque churches and chapels surrounded by fields full of hay.
I&#x27;ve made a lot of progress.
The strength and endurance in my calves, ankles and feet has grown significantly, 
same as my cardiovascular fitness.
However, my physical gains are nothing compared to the progress of my approach.
All my life I thought running was inherently exhausting, which is not true.
My approach has made it so.
Always running as fast as possible and constantly out of breath, I was a very 
inefficient runner who never conserved energy during lighter stretches.
Turns out you can run at a speed where it feels like you actually gain more 
energy rather than depleting your reserves.
When hitting the sweet spot I feel like I can run forever, a state many runners 
have told me about but I thought was made up.
As to using physical exercise as punishment to rid myself from an anxious and 
restless mind like I did during the first run, it has not happened since then.
Partly due to adding regular running to my self-care routine has kept my mind
in a calm and healthy state. 
But also because I&#x27;m starting to doubt the effectiveness of this behaviour more 
and more with each time I exhibit it.
Consuming all the energy for something futile and self-destructive just to feel
like hitting the reset button is wasteful and time-consuming. 
There must be a more efficient way or else more people would be long-distance 
runners or indulge in self-flagellation.
Right now I feel like Alexis Zorba did after he gorged himself on sherries till
he got sick, to rid himself from his addiction to them.
Maybe I did the same with my need for penance for what I perceive to be a 
weakness.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;references&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#references&quot; aria-label=&quot;Anchor link for: references&quot;&gt;#&lt;&#x2F;a&gt;
References&lt;&#x2F;h1&gt;
&lt;p&gt;[&lt;a name=&quot;ref-1&quot;&gt;1&lt;&#x2F;a&gt;] Dennis M. Bramble and Daniel E. Lieberman.
2004. Endurance running and the evolution of homo. &lt;em&gt;Nature&lt;&#x2F;em&gt; 432, 7015
(November 2004), 345–352.
DOI:&lt;a href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.1038&#x2F;nature03052&quot;&gt;https:&#x2F;&#x2F;doi.org&#x2F;10.1038&#x2F;nature03052&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;[&lt;a name=&quot;ref-2&quot;&gt;2&lt;&#x2F;a&gt;] Irene S. Davis, Tony Lin-Wei Chen, and Scott C.
Wearing. 2022. Reversing the mismatch with forefoot striking to reduce
running injuries. &lt;em&gt;Frontiers in Sports and Active Living&lt;&#x2F;em&gt; 4, (May 2022).
DOI:&lt;a href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.3389&#x2F;fspor.2022.794005&quot;&gt;https:&#x2F;&#x2F;doi.org&#x2F;10.3389&#x2F;fspor.2022.794005&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;[&lt;a name=&quot;ref-3&quot;&gt;3&lt;&#x2F;a&gt;] Joseph Hamill and Allison H. Gruber. 2017. Is
changing footstrike pattern beneficial to runners? &lt;em&gt;Journal of Sport and
Health Science&lt;&#x2F;em&gt; 6, 2 (June 2017), 146–153.
DOI:&lt;a href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.1016&#x2F;j.jshs.2017.02.004&quot;&gt;https:&#x2F;&#x2F;doi.org&#x2F;10.1016&#x2F;j.jshs.2017.02.004&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;[&lt;a name=&quot;ref-4&quot;&gt;4&lt;&#x2F;a&gt;] Xiaochen Lin, Xi Zhang, Jianjun Guo, Christian
K. Roberts, Steve McKenzie, Wen‐Chih Wu, Simin Liu, and Yiqing Song.
2015. Effects of exercise training on cardiorespiratory fitness and
biomarkers of cardiometabolic health: A systematic review and
meta‐analysis of randomized controlled trials. &lt;em&gt;Journal of the American
Heart Association&lt;&#x2F;em&gt; 4, 7 (July 2015).
DOI:&lt;a href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.1161&#x2F;jaha.115.002014&quot;&gt;https:&#x2F;&#x2F;doi.org&#x2F;10.1161&#x2F;jaha.115.002014&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>How to Automate Publishing your Crates with GitHub Actions</title>
        <published>2023-04-21T15:00:00+00:00</published>
        <updated>2023-04-21T15:00:00+00:00</updated>
        <author>
          <name>Jonas Fassbender</name>
        </author>
        <link rel="alternate" href="https://fassbender.dev/blog/001-cargo-publish-action/" type="text/html"/>
        <id>https://fassbender.dev/blog/001-cargo-publish-action/</id>
        <content type="html">&lt;div style=&quot;text-align: center&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;xkcd.com&#x2F;1319&#x2F;&quot;&gt;&lt;img src=&quot;https:&#x2F;&#x2F;imgs.xkcd.com&#x2F;comics&#x2F;automation.png&quot; alt=&quot;Automation&quot; &#x2F;&gt;&lt;&#x2F;a&gt; &lt;&#x2F;p&gt;
&lt;p&gt;Figure 1: &lt;em&gt;I also think that if the task is not time-consuming at all&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;I wouldn&#x27;t be able to call myself a DevOps Engineer&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; if I wouldn&#x27;t automate even
the silliest and most banal of tasks.
Not even when I know that I will spend considerably more time writing the
code (and debugging it!) than it will ever save me.
In this post, we will have a look at how to automate publishing your Rust crate. &lt;&#x2F;p&gt;
&lt;p&gt;Publishing your crate is one of the most complex deployments out there. 
On par with publishing your app in the Apple App Store or updating your
Kubernetes deployment across availability zones, I&#x27;d say.
It requires you to run this command:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span&gt;&amp;gt; cargo &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;publish 
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I know, I know. It&#x27;s all very scary looking. 
Hard not to screw up.
It requires you to type a whopping two words containing a grand total of twelve 
characters. 
Twelve characters! 
Who in the world has the mental capacity to remember such a lengthy and
completely convoluted command?&lt;&#x2F;p&gt;
&lt;p&gt;All jokes and irony aside, I am a firm believer that every software project 
should have a sound CI&#x2F;CD setup and I think that this setup should contain the 
seemingly trivial tasks, even when the automation looks a lot more complex than 
the task itself.
A late adopter of the CD (continuous deployment) part of CI&#x2F;CD, I learned that
there is more to a release than meets the eye, even with seemingly simple 
deployments like a crate distributed via Cargo.
Creating a release always requires you to execute more than just a single 
command.
There are simple things that need to be done, like tagging the release commit. 
But publishing a crate can also be embedded in a bigger and more 
complex release setup that consists of multiple deployments to different 
registries and other places like package managers or app stores.&lt;&#x2F;p&gt;
&lt;p&gt;The moment you have to execute more than a single instruction to complete a 
task, you enter the land of automation. 
I&#x27;ve messed up releases before. 
So far just annoying little things like forgetting to create the Git tag or 
creating a tag that has the wrong version.
But as a notorious over-thinker and perfectionist, the thought of leaving a 
project in such a subtly inconsistent state makes me shiver.
Luckily there are other neurotic people out there that came up with the idea
of CI&#x2F;CD and convenient and powerful tools like 
&lt;a href=&quot;https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;actions&quot;&gt;GitHub Actions&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This post contains a step-by-step tutorial on how to set up a GitHub Actions
workflow that publishes your crate to &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&quot;&gt;crates.io&lt;&#x2F;a&gt;  when you 
push a tag that looks like a &lt;a href=&quot;https:&#x2F;&#x2F;semver.org&#x2F;&quot;&gt;SemVer&lt;&#x2F;a&gt; version (without 
extensions).
This tutorial expects you to have a basic knowledge of GitHub Actions, a GitHub 
repository with a crate you wish to publish and an account on crates.io.
Except the first part (where we get an API token from crates.io), this 
tutorial extends to publishing a crate to 
&lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;cargo&#x2F;reference&#x2F;registries.html&quot;&gt;registries&lt;&#x2F;a&gt; other 
than crates.io as well.
The complete workflow can be found in &lt;a href=&quot;https:&#x2F;&#x2F;fassbender.dev&#x2F;blog&#x2F;001-cargo-publish-action&#x2F;#6-final-workflow&quot;&gt;this&lt;&#x2F;a&gt; section.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;1-get-an-api-token-from-crates-io&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#1-get-an-api-token-from-crates-io&quot; aria-label=&quot;Anchor link for: 1-get-an-api-token-from-crates-io&quot;&gt;#&lt;&#x2F;a&gt;
1. Get an API Token from crates.io&lt;&#x2F;h1&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;fassbender.dev&#x2F;blog&#x2F;001-cargo-publish-action&#x2F;crates_io_1.jpg&quot; alt=&quot;crates.io: navigate to API tokens&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Figure 2: &lt;em&gt;How to navigate to&lt;&#x2F;em&gt; 
&lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;settings&#x2F;tokens&quot;&gt;&lt;code&gt;https:&#x2F;&#x2F;crates.io&#x2F;settings&#x2F;tokens&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; 
&lt;em&gt;from within the webapp&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;First, we need to authenticate ourselves with crates.io.
The registry needs to know who we are and if we in fact have the right to 
publish our crate.
For this, crates.io offers API tokens.
You can generate a new API token in the web UI.
To do that, make sure you are signed into crates.io in your browser (I registered with 
crates.io with my GitHub account which makes SSO nice and easy).
Once you are signed in, navigate to &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;settings&#x2F;tokens&quot;&gt;&lt;code&gt;https:&#x2F;&#x2F;crates.io&#x2F;settings&#x2F;tokens&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.
In the web UI you have a drop-down menu in the upper right corner, next to your 
avatar and username. 
It contains an entry &amp;quot;Account Settings&amp;quot;. 
The account settings contain the page &amp;quot;API Tokens&amp;quot; where we can manage our
tokens. &lt;&#x2F;p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;fassbender.dev&#x2F;blog&#x2F;001-cargo-publish-action&#x2F;crates_io_2.jpg&quot; alt=&quot;crates.io: create new API token&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Figure 3: &lt;em&gt;Create a new API token&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;There&#x27;s a button on the API tokens page that says &amp;quot;New Token&amp;quot;.
Click on it.
A new text field will open below.
Type in the name you want to associate with the new token.
I normally use the crate name, that way I know where I used the token.
This&#x27;d make deleting the token after it accidentally got leaked easier.
Once you&#x27;ve typed the token&#x27;s name, click on the &amp;quot;Create&amp;quot; button next to the
text input.
The token will be displayed below.
Copy the token and don&#x27;t close the page until you have successfully stored
the token safely (you&#x27;ll never be able to see the token again after closing the
page!).&lt;&#x2F;p&gt;
&lt;h1 id=&quot;2-store-the-api-token-in-a-github-secret&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#2-store-the-api-token-in-a-github-secret&quot; aria-label=&quot;Anchor link for: 2-store-the-api-token-in-a-github-secret&quot;&gt;#&lt;&#x2F;a&gt;
2. Store the API Token in a GitHub Secret&lt;&#x2F;h1&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;p&gt;&lt;a name=&quot;fig-navigate-to-secret&quot;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;fassbender.dev&#x2F;blog&#x2F;001-cargo-publish-action&#x2F;github_secrets_1.jpg&quot; alt=&quot;GitHub: navigate to new Action secret&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Figure 4: &lt;em&gt;How to navigate to&lt;&#x2F;em&gt; &lt;code&gt;https:&#x2F;&#x2F;github.com&#x2F;{user or orga}&#x2F;{repo}&#x2F;settings&#x2F;secrets&#x2F;actions&#x2F;new&lt;&#x2F;code&gt;
&lt;em&gt;from within the webapp&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Once we have copied our API token from crates.io to our clipboard, we have to
make it available to our workflow.
The token must stay a secret (otherwise people could publish malicious content
to crates.io using our credentials or &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;cargo&#x2F;reference&#x2F;publishing.html#cargo-yank&quot;&gt;yank&lt;&#x2F;a&gt; 
our crates).
We can store the token securely on GitHub in an &lt;a href=&quot;https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;actions&#x2F;security-guides&#x2F;encrypted-secrets&quot;&gt;Action secret&lt;&#x2F;a&gt;.
You can create a new secret for the repository containing your crate under
&lt;code&gt;https:&#x2F;&#x2F;github.com&#x2F;{user or orga}&#x2F;{repo}&#x2F;settings&#x2F;secrets&#x2F;actions&#x2F;new&lt;&#x2F;code&gt;.
You can navigate to the page from your repository.
You can find it under &lt;em&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions&lt;&#x2F;em&gt; (see
Figure &lt;a href=&quot;https:&#x2F;&#x2F;fassbender.dev&#x2F;blog&#x2F;001-cargo-publish-action&#x2F;#fig-navigate-to-secret&quot;&gt;4&lt;&#x2F;a&gt;).&lt;&#x2F;p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
&lt;p&gt;&lt;a name=&quot;fig-create-secret&quot;&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;fassbender.dev&#x2F;blog&#x2F;001-cargo-publish-action&#x2F;github_secrets_2.jpg&quot; alt=&quot;GitHub: create new Action secret&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Figure 5: &lt;em&gt;Create a new GitHub Action secret&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;The page you&#x27;ll see is depicted in Figure &lt;a href=&quot;https:&#x2F;&#x2F;fassbender.dev&#x2F;blog&#x2F;001-cargo-publish-action&#x2F;#fig-create-secret&quot;&gt;5&lt;&#x2F;a&gt;. 
You have two text inputs, one for the name of the secret, one for the secret
itself.
Type in the name of your secret.
I use &lt;code&gt;CARGO_REGISTRY_TOKEN&lt;&#x2F;code&gt; for the secret name, as it is the name of the
&lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;cargo&#x2F;reference&#x2F;config.html#registrytoken&quot;&gt;environment variable&lt;&#x2F;a&gt; 
Cargo uses to look up the token during publishing.
We&#x27;ll use the secret name explicitly in our workflow, mapping it to the 
environment variable, so you can name the secret however you like.
Paste the secret from the clipboard into the second text input labeled 
&amp;quot;Secret&amp;quot;.
Click on the &amp;quot;Add secret&amp;quot; button below to create the secret.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;using-the-github-cli-to-create-the-secret&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#using-the-github-cli-to-create-the-secret&quot; aria-label=&quot;Anchor link for: using-the-github-cli-to-create-the-secret&quot;&gt;#&lt;&#x2F;a&gt;
Using the GitHub CLI to Create the Secret&lt;&#x2F;h4&gt;
&lt;p&gt;Instead of using GitHub&#x27;s web UI, you can also use &lt;a href=&quot;https:&#x2F;&#x2F;cli.github.com&#x2F;&quot;&gt;GitHub CLI&lt;&#x2F;a&gt;,
which you may find more convenient.
Open your console and navigate to your repository.
Inside your repository, run the following command to create the secret:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;gh&lt;&#x2F;span&gt;&lt;span&gt; secret set CARGO_REGISTRY_TOKEN&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -b &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;TOKEN&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;$TOKEN&lt;&#x2F;code&gt; is your API token from crates.io. 
On Linux with X11, you can insert the token from your clipboard easily with:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;gh&lt;&#x2F;span&gt;&lt;span&gt; secret set CARGO_REGISTRY_TOKEN&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -b &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;$&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;xclip -o&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can find the documentation for the &lt;code&gt;gh secret set&lt;&#x2F;code&gt; command 
&lt;a href=&quot;https:&#x2F;&#x2F;cli.github.com&#x2F;manual&#x2F;gh_secret_set&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;3-write-the-workflow&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#3-write-the-workflow&quot; aria-label=&quot;Anchor link for: 3-write-the-workflow&quot;&gt;#&lt;&#x2F;a&gt;
3. Write the Workflow&lt;&#x2F;h1&gt;
&lt;p&gt;Now that we have our API token in place, we can finally get to the fun part:
writing the actual workflow.
Workflows are yaml files that live in the &lt;code&gt;.github&#x2F;workflows&lt;&#x2F;code&gt; directory of your 
repository.
Let&#x27;s create a new workflow &lt;code&gt;.github&#x2F;workflows&#x2F;publish.yml&lt;&#x2F;code&gt; and start with
giving it a name:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-yaml &quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Publish
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Okay now, that was not very spicy. 
Let&#x27;s get to a more fun section of our workflow we have to define, the trigger.
We want to trigger the publishing workflow when we create a tag that looks like
the version of our crate.
The version of our crate can be found in the &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; manifest file in the
&lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;cargo&#x2F;reference&#x2F;manifest.html#the-version-field&quot;&gt;&lt;code&gt;package.version&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; 
field.
Crates must adhere to semantic versioning, as it is used by Cargo to check for
compatible versions during dependency resolution.
Without going into much detail, SemVer version numbers consist of three numeric
parts (called major, minor and patch) separated by dots, like &lt;code&gt;1.0.1&lt;&#x2F;code&gt;, or 
&lt;code&gt;0.1.1234&lt;&#x2F;code&gt;, for example.
Semantic versioning also supports additional labels and metadata—so called
extensions—after the version number, like &lt;code&gt;1.0.0-beta.12&lt;&#x2F;code&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.
Tag filters can be described with a 
&lt;a href=&quot;https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;actions&#x2F;using-workflows&#x2F;workflow-syntax-for-github-actions#filter-pattern-cheat-sheet&quot;&gt;glob pattern&lt;&#x2F;a&gt;
that looks a little bit like a regular expression.
Let&#x27;s have a look at the trigger already:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-yaml &quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;on&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tags&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;v[0-9]+.[0-9]+.[0-9]+
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;a href=&quot;https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;actions&#x2F;using-workflows&#x2F;workflow-syntax-for-github-actions#on&quot;&gt;&lt;code&gt;on&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; 
field looks pretty straight-forward.
Every time we push a tag that looks like &lt;code&gt;v[0-9]+.[0-9]+.[0-9]+&lt;&#x2F;code&gt;, run this
workflow.
For example, when we push a tag &lt;code&gt;v0.1.0&lt;&#x2F;code&gt;, this workflow would be executed, 
whereas a tag &lt;code&gt;some-tag&lt;&#x2F;code&gt; would not trigger this workflow, as it is not matched 
by the glob pattern.
Note that if you prefer your version tags without the leading &lt;code&gt;v&lt;&#x2F;code&gt;, all you have
to do is remove the &lt;code&gt;v&lt;&#x2F;code&gt; from the glob pattern: &lt;code&gt;[0-9]+.[0-9]+.[0-9]+&lt;&#x2F;code&gt;.
This would match a tag consisting solely of a SemVer version number, like 
&lt;code&gt;1.2.3&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Now that we have our trigger, we have to tell GitHub what to do.
There are basically only two steps necessary to publish our crate, (I) make
the source code available in our workflow runner and (II) tell Cargo to publish 
it.
But first GitHub needs to know what kind of machine and container we&#x27;d like 
our workflow to run in.
We&#x27;ll use a fairly common setup—in Rust world—for that, namely the 
latest Ubuntu LTS version as basis and on top of that the latest release of the 
&lt;a href=&quot;https:&#x2F;&#x2F;hub.docker.com&#x2F;_&#x2F;rust&quot;&gt;Rust container&lt;&#x2F;a&gt; that makes the necessary tools
to publish our crate available to us:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-yaml &quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;jobs&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Publish&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;runs-on&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ubuntu-latest
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;container&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;image&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;rust:latest
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now that we have our basic runner setup, we have to write the necessary steps 
our runner should perform.
To get the source code from our repository, we&#x27;ll use the 
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;marketplace&#x2F;actions&#x2F;checkout&quot;&gt;checkout&lt;&#x2F;a&gt; action.
It will pull our repository into the workflow, allowing it access to the 
contents, i.e. the crate&#x27;s source code:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;diff&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-diff &quot;&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span&gt; jobs:
&lt;&#x2F;span&gt;&lt;span&gt;   Publish:
&lt;&#x2F;span&gt;&lt;span&gt;     runs-on: ubuntu-latest
&lt;&#x2F;span&gt;&lt;span&gt;     container:
&lt;&#x2F;span&gt;&lt;span&gt;       image: rust:latest
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+    steps:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+      - name: Checkout repository
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+        uses: actions&#x2F;checkout@v3
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Everything is in place now for Cargo to do its job and publish our crate.
All we have to do is run &lt;code&gt;cargo publish&lt;&#x2F;code&gt; with our API token exposed in the
&lt;code&gt;CARGO_REGISTRY_TOKEN&lt;&#x2F;code&gt; environment variable and Cargo will do the rest:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;diff&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-diff &quot;&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span&gt; jobs:
&lt;&#x2F;span&gt;&lt;span&gt;   Publish:
&lt;&#x2F;span&gt;&lt;span&gt;     runs-on: ubuntu-latest
&lt;&#x2F;span&gt;&lt;span&gt;     container:
&lt;&#x2F;span&gt;&lt;span&gt;       image: rust:latest
&lt;&#x2F;span&gt;&lt;span&gt;     steps:
&lt;&#x2F;span&gt;&lt;span&gt;       - name: Checkout repository
&lt;&#x2F;span&gt;&lt;span&gt;         uses: actions&#x2F;checkout@v3
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+      - name: Publish
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+        run: cargo publish
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+        env:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Et voilà! Not hard to setup a GitHub Action workflow that publishes your crate
to crates.io at all!
Note that if you used a different name than &lt;code&gt;CARGO_REGISTRY_TOKEN&lt;&#x2F;code&gt; for the 
secret with the API token from crates.io, you have to change 
&lt;code&gt;${{ secrets.CARGO_REGISTRY_TOKEN }}&lt;&#x2F;code&gt; to &lt;code&gt;${{ secrets.YOUR_SECRET_NAME }}&lt;&#x2F;code&gt;.
Here our whole workflow at this point:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-yaml &quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Publish
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;on&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tags&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;v[0-9]+.[0-9]+.[0-9]+
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;jobs&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Publish&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;runs-on&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ubuntu-latest
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;container&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;image&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;rust:latest
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;steps&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Checkout repository
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;uses&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;actions&#x2F;checkout@v3
&lt;&#x2F;span&gt;&lt;span&gt;      - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Publish
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;cargo publish
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;env&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;CARGO_REGISTRY_TOKEN&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;${{ secrets.CARGO_REGISTRY_TOKEN }}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h4 id=&quot;publishing-to-registries-other-than-crates-io&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#publishing-to-registries-other-than-crates-io&quot; aria-label=&quot;Anchor link for: publishing-to-registries-other-than-crates-io&quot;&gt;#&lt;&#x2F;a&gt;
Publishing to Registries other than Crates.io&lt;&#x2F;h4&gt;
&lt;p&gt;So far we only looked at publishing to crates.io, the default registry used by
Cargo.
What if you want to publish to a different registry?
Let&#x27;s have a look how we can set up our workflow to publish to another
registry.
Note that in this tutorial we want to configure Cargo solely from within the 
workflow, not with a &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;cargo&#x2F;reference&#x2F;config.html&quot;&gt;configuration file&lt;&#x2F;a&gt;.
Let&#x27;s use the example from the &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;cargo&#x2F;reference&#x2F;registries.html&quot;&gt;Cargo Book&lt;&#x2F;a&gt;.
Say you want to publish to the &lt;code&gt;my-registry&lt;&#x2F;code&gt; registry, whose index is located 
at &lt;code&gt;https:&#x2F;&#x2F;my-intranet:8080&#x2F;git&#x2F;index&lt;&#x2F;code&gt;.
To tell Cargo to use a different registry than the default registry, we can
use the &lt;code&gt;--registry&lt;&#x2F;code&gt; flag:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cargo&lt;&#x2F;span&gt;&lt;span&gt; publish&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; --registry&lt;&#x2F;span&gt;&lt;span&gt;=my-registry
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We still have to provide an API token to the new registry.
This must be done with a different environment variable than 
&lt;code&gt;CARGO_REGISTRY_TOKEN&lt;&#x2F;code&gt;, which we used before, as it only works for crates.io.
For a different registry, Cargo uses the &lt;code&gt;CARGO_REGISTRIES_&amp;lt;name&amp;gt;_TOKEN&lt;&#x2F;code&gt; 
environment variable, where &lt;code&gt;&amp;lt;name&amp;gt;&lt;&#x2F;code&gt; is the stylised name of our registry 
(stylised to uppercase and dashes are converted to underscores).
In the &lt;code&gt;my-registry&lt;&#x2F;code&gt; case, the environment variable would be 
&lt;code&gt;CARGO_REGISTRIES_MY_REGISTRY_TOKEN&lt;&#x2F;code&gt;.
Unfortunately, we are not done yet.
Cargo knows where it can find the index of crates.io, but we have to tell it
where it finds the index of &lt;code&gt;my-registry&lt;&#x2F;code&gt;.
So we need to add another environment variable with the index. 
It looks nearly identical with the environment variable with the API token:
&lt;code&gt;CARGO_REGISTRIES_&amp;lt;name&amp;gt;_INDEX&lt;&#x2F;code&gt;. Here the changes to the workflow to publish to 
&lt;code&gt;my-registry&lt;&#x2F;code&gt;, instead of crates.io:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;diff&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-diff &quot;&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span&gt; name: Publish
&lt;&#x2F;span&gt;&lt;span&gt; on:
&lt;&#x2F;span&gt;&lt;span&gt;   push:
&lt;&#x2F;span&gt;&lt;span&gt;     tags:
&lt;&#x2F;span&gt;&lt;span&gt;       - v[0-9]+.[0-9]+.[0-9]+
&lt;&#x2F;span&gt;&lt;span&gt; jobs:
&lt;&#x2F;span&gt;&lt;span&gt;   Publish:
&lt;&#x2F;span&gt;&lt;span&gt;     runs-on: ubuntu-latest
&lt;&#x2F;span&gt;&lt;span&gt;     container:
&lt;&#x2F;span&gt;&lt;span&gt;       image: rust:latest
&lt;&#x2F;span&gt;&lt;span&gt;     steps:
&lt;&#x2F;span&gt;&lt;span&gt;       - name: Checkout repository
&lt;&#x2F;span&gt;&lt;span&gt;         uses: actions&#x2F;checkout@v3
&lt;&#x2F;span&gt;&lt;span&gt;       - name: Publish
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-        run: cargo publish
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+        run: cargo publish --registry=my-registry
&lt;&#x2F;span&gt;&lt;span&gt;         env:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+          CARGO_REGISTRIES_MY_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+          CARGO_REGISTRIES_MY_REGISTRY_INDEX: https:&#x2F;&#x2F;my-intranet:8080&#x2F;git&#x2F;index
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;4-optional-comparing-cargo-toml-version-with-tag&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#4-optional-comparing-cargo-toml-version-with-tag&quot; aria-label=&quot;Anchor link for: 4-optional-comparing-cargo-toml-version-with-tag&quot;&gt;#&lt;&#x2F;a&gt;
4. Optional: Comparing Cargo.toml Version with Tag&lt;&#x2F;h1&gt;
&lt;p&gt;While we automated crate publishing just now, there&#x27;s an easy-to-make error we
haven&#x27;t protected us from yet.
Namely using a tag that does not match the version number of our crate.
As stated above, Cargo uses the &lt;code&gt;package.version&lt;&#x2F;code&gt; field from the &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt;
manifest file of your crate to determine the version that is published.
Say we want to publish version &lt;code&gt;0.2.1&lt;&#x2F;code&gt; of our crate.
So far, nothing hinders us from triggering the workflow with a tag that looks
like a SemVer version different from the one in our manifest. 
For the paranoids, this is not good enough.
Having release &lt;code&gt;0.2.1&lt;&#x2F;code&gt; tagged as &lt;code&gt;0.2.2&lt;&#x2F;code&gt; is not acceptable.
So we need to compare the version in our manifest with the tag and make sure 
they are equal.&lt;&#x2F;p&gt;
&lt;p&gt;Getting the name of the tag we pushed is easy. 
It is available to our workflow in the &lt;code&gt;github.ref_name&lt;&#x2F;code&gt; property of the 
&lt;code&gt;github&lt;&#x2F;code&gt; &lt;a href=&quot;https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;actions&#x2F;learn-github-actions&#x2F;contexts&quot;&gt;context&lt;&#x2F;a&gt;.
Getting the value of the &lt;code&gt;package.version&lt;&#x2F;code&gt; field from &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; is more 
tricky.
We need a tool that can parse &lt;code&gt;toml&lt;&#x2F;code&gt; files and extract the information we need
from it.
Luckily there is such a tool available that is easy to get with the tools we
already have installed in our runner: &lt;a href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;toml-cli&quot;&gt;&lt;code&gt;toml-cli&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.
&lt;code&gt;toml-cli&lt;&#x2F;code&gt; is a CLI program distributed as a Rust binary on crates.io.
Binaries from crates.io can be installed with 
&lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;cargo&#x2F;commands&#x2F;cargo-install.html&quot;&gt;&lt;code&gt;cargo install&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cargo&lt;&#x2F;span&gt;&lt;span&gt; install toml-cli
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After we have installed &lt;code&gt;toml-cli&lt;&#x2F;code&gt;, we can extract the version from our 
manifest file with the following command:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;toml&lt;&#x2F;span&gt;&lt;span&gt; get&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -r&lt;&#x2F;span&gt;&lt;span&gt; Cargo.toml package.version
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;All that is left is compare the extracted version from our manifest with the
pushed tag.
We&#x27;ll use another command that we already have available in our runner:
Bash&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;www.man7.org&#x2F;linux&#x2F;man-pages&#x2F;man1&#x2F;test.1.html&quot;&gt;&lt;code&gt;test&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; command.
It allows us to compare two strings—the version from our manifest and our
tag—and fail the workflow if they are not equal (by returning an exit 
code other than zero).
Here&#x27;s how we install &lt;code&gt;toml-cli&lt;&#x2F;code&gt; and compare manifest version with the tag, in
two easy steps:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;diff&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-diff &quot;&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span&gt; name: Publish
&lt;&#x2F;span&gt;&lt;span&gt; on:
&lt;&#x2F;span&gt;&lt;span&gt;   push:
&lt;&#x2F;span&gt;&lt;span&gt;     tags:
&lt;&#x2F;span&gt;&lt;span&gt;       - v[0-9]+.[0-9]+.[0-9]+
&lt;&#x2F;span&gt;&lt;span&gt; jobs:
&lt;&#x2F;span&gt;&lt;span&gt;   Publish:
&lt;&#x2F;span&gt;&lt;span&gt;     runs-on: ubuntu-latest
&lt;&#x2F;span&gt;&lt;span&gt;     container:
&lt;&#x2F;span&gt;&lt;span&gt;       image: rust:latest
&lt;&#x2F;span&gt;&lt;span&gt;     steps:
&lt;&#x2F;span&gt;&lt;span&gt;       - name: Checkout repository
&lt;&#x2F;span&gt;&lt;span&gt;         uses: actions&#x2F;checkout@v3
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+      - name: Install toml-cli
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+        run: cargo install toml-cli
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+      - name: Check version
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+        run: test &amp;quot;v$(toml get -r Cargo.toml package.version)&amp;quot; = &amp;quot;${{ github.ref_name }}&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;       - name: Publish
&lt;&#x2F;span&gt;&lt;span&gt;         run: cargo publish
&lt;&#x2F;span&gt;&lt;span&gt;         env:            
&lt;&#x2F;span&gt;&lt;span&gt;           CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you don&#x27;t use the &lt;code&gt;v&lt;&#x2F;code&gt; in front of your version number in your release tags,
you have to remove the &lt;code&gt;v&lt;&#x2F;code&gt; in front of the embedded call to &lt;code&gt;toml-cli&lt;&#x2F;code&gt; in the
&amp;quot;Check version&amp;quot; step.
If your &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; manifest file is not located in the root directory of your
repository, you&#x27;d have to change the path to in the call to &lt;code&gt;toml-cli&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;toml&lt;&#x2F;span&gt;&lt;span&gt; get&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -r&lt;&#x2F;span&gt;&lt;span&gt; path&#x2F;to&#x2F;Cargo.toml package.version
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;6-final-workflow&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#6-final-workflow&quot; aria-label=&quot;Anchor link for: 6-final-workflow&quot;&gt;#&lt;&#x2F;a&gt;
6. Final Workflow&lt;&#x2F;h1&gt;
&lt;p&gt;In this tutorial we looked at how to automate publishing a Rust crate with a
GitHub Actions workflow.
We created a trigger that runs the workflow whenever we push a tag that looks
like a SemVer version number (with a leading &lt;code&gt;v&lt;&#x2F;code&gt; and without) and performs the
necessary steps to publish our crate (to crates.io or another registry).
Furthermore we made the workflow more bullet-proof by adding a test that makes
sure the crate version in our &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; manifest file matches the tag we
pushed, to avoid having releases labeled with the wrong version number.
Here the final workflow:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-yaml &quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Publish
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;on&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tags&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;v[0-9]+.[0-9]+.[0-9]+
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;jobs&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Publish&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;runs-on&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ubuntu-latest
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;container&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;image&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;rust:latest
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;steps&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Checkout repository
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;uses&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;actions&#x2F;checkout@v3
&lt;&#x2F;span&gt;&lt;span&gt;      - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Install toml-cli
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;cargo install toml-cli
&lt;&#x2F;span&gt;&lt;span&gt;      - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Check version
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;test &amp;quot;v$(toml get -r Cargo.toml package.version)&amp;quot; = &amp;quot;${{ github.ref_name }}&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;      - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Publish
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;cargo publish
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;env&lt;&#x2F;span&gt;&lt;span&gt;:            
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;CARGO_REGISTRY_TOKEN&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;${{ secrets.CARGO_REGISTRY_TOKEN }}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;hr &#x2F;&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;I don&#x27;t call me that, but I enjoy dabbling with modern concepts of DevOps
like CI&#x2F;CD, artifact registries, containerisation, Kubernetes and cloud 
computing. 
I also love writing my own CLI programs and scripts to make Ops as easy and 
convenient for me as possible.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;This tutorial omits the support for extensions, focusing solely on the 
version number.
You can find regular expressions for matching SemVer versions correctly 
&lt;a href=&quot;https:&#x2F;&#x2F;semver.org&#x2F;#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string&quot;&gt;here&lt;&#x2F;a&gt;,
in case you have the need to extend the workflow trigger to contain such.
GitHub&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;actions&#x2F;using-workflows&#x2F;workflow-syntax-for-github-actions#filter-pattern-cheat-sheet&quot;&gt;glob patterns&lt;&#x2F;a&gt; 
are not very powerful compared to regular expressions.
Therefore it&#x27;s quite hard (if not impossible) to write a universal filter
that&#x27;d match all possible SemVer versions, while not matching a string that 
is not a valid SemVer version. 
I leave it up to you to write a glob pattern that fits your use of SemVer 
extensions.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
    </entry>
</feed>
