In the earliest days of Single we had no good way of administering our system. If we needed to make modifications to data that couldn’t be done by interacting with the customer-facing UI, I had to execute database queries on the database server manually. If there was some back-end process that needed to be triggered to replay actions or fix a failed process, I would make endpoints in the API and hit them with a REST client like Postman. While both of those things will work for the most part, they’re time-consuming and error prone at best.
It was clear we needed a better way to administer our system. But, without enough time or developer resources (we’re a very small team), we weren’t able to build an administration UI for these kinds of tasks. So, I decided to build a CLI (command-line interface) that we could use to easily query data and perform common maintenance tasks. Here's some of the most basic commands:
$ single version
single version 4.1.0 [ tammy ] built on 2019-07-12_06:10:16PM
$ single -h
The Single CLI
Usage:
single [flags]
single [command]
Available Commands:
7digital 7Digital Operations
account Operations on accounts
album Operations on albums
album-product Album product commands
artist artist operations
bill Account bill operations
billing Operations on billing
boostlink Boostlink Operations
boostlink-type Boostlink Type Operations
buzzangle Buzzangle Report Operations
cascading-track Cascading Track operations
code Download Code operations
email Email history
help Help about any command
job Operations on scheduled jobs
login Login to Auth0
order Operations on orders
physical-album Operations on physical albums
purchase Purchase history
set-env Set the environment in config.
shopify Shopify Operations
short-link Link shortening operations
soundscan Soundscan Report Operations
storage file storage and processing operations
track Operations on tracks
trigger Operations on scheduled job triggers
user Operations on users
variant-migration Variant Migration Commands
version Print the version of the CLI
white-label Operations on white labels
Flags:
-h, --help help for single
Use "single [command] --help" for more information about a command.
Today, the CLI has evolved to be the Swiss army knife of our system. We use it to update incorrect data on releases, trigger failed processes to run again, re-play event streams from Shopify, and query for data when troubleshooting, among other things. The CLI is our main window into the state of our system.
From a technical perspective, the CLI acts as a client to our back end APIs. Input data is passed to commands as flags and arguments. Data is then returned in JSON format (with some tabular and text-based exceptions). Almost every API endpoint in our system is accompanied with a corresponding CLI function. I’ve even ended up using the CLI as a testing tool — to exercise parts of the system as I’m building them. The configuration is set up to be switchable between environments, so we can execute commands against production, staging, and local environments.
What’s interesting about this CLI approach is that the executable can be easily combined with existing command-line tools to extend its functionality. Because the output of most commands is in JSON format, jq
can be use to re-format, filter, and search the data. For example, here’s a command for loading basic information about an account:
$ single account summary -d single-fh-dev-3
{
"accountId": 1251242141,
"active": true,
"myshopifyDomain": "single-fh-dev-3.myshopify.com",
"name": "single-fh-dev-3",
"shopifyChargeApproved": true,
"users": [
{
"accountUserId": 125122457541,
"email": "taylor@singlemusic.co"
},
{
"accountUserId": 547345632564,
"email": "admin@singlemusic.com"
}
]
}
Now, lets say we just want the email addresses associated with the account (kind of a silly example in this case, but you get the idea). Using jq
the command and output become:
$ single account summary -d single-fh-dev-3 | jq -r '.users[] | .email'
admin@singlemusic.com
taylor@singlemusic.co
As another example, we can use grep
and wc
to count the total number of accounts from the command that lists all the accounts like so:
$ single account all | grep accountId | wc -l
436
Because the CLI executes in a shell, we can also use shell scripting to create all kinds of functionality--using the output of certain commands as input for others, looping over files, etc. Scripts can of course be saved for re-use as well.
The Single CLI is written in Go, using the cobra framework with viper for configuration. Our continuous integration system runs the Docker-based build process that builds a binary executable which is then shipped to an artifact repository so our internal users can simply download it to run it — with a little configuration setup.
So, if you find yourself in need of some administration tools for your API, and don't have enough time to make a UI, consider creating a CLI for those tasks. There's a little bit of a learning curve for less-technically-inclided people, but they'll eventually get the hang of it and be productive. Structure your output in JSON or some other easily-computer-consumable format so that you can combine it with other CLI tools to extend functionality