Crafting a Useful & Extensible (Company) CLI

David Freilich
AppsFlyer Engineering
6 min readJan 6, 2022

Spurring Adoption

As a backend developer, I know the fear. A popular problem could be solved with a beautiful script or CLI (a command-line interface) but you have one lingering concern. How can you ensure that it won’t just end up in your users /usr/wastebin, even if it is downloaded? How can you give your tool the best chance at long-term adoption?

At AppsFlyer, we’re working on a new CLI initiative, and taking steps to solve this problem. My background is in CLI construction, most recently as part of the open-source Cloud Native Buildpacks project, where I am a maintainer of pack, a tool for transforming source code into Open Containers Initiative (OCI) images (aka Docker images). As such, I was excited to take some of my learnings into driving this effort.

In this post, we’ll give some background on our problem, describe our current efforts to write a CLI, and give the design principles that we are following to ensure that our CLI effort thrives.

One element that we won’t be talking about is external advertising and promotion. Additionally, this post is more backend focused, but will provide value to any tool author.

You Get a CLI

The Platform Group at AppsFlyer creates tools and maintains environments for the R&D organization, to ensure that engineers can move fast safely. One tool that we provide for all of R&D is a CLI to interact with an internal testing tool.

As time passed, we noticed that a number of CLIs were built to interact with the exact same tool as ours did, with a better and clearer UX. Moreover, we saw that recognition for our CLI had dramatically dropped — many teams didn’t even know we had created an “official” CLI, and only knew of the other CLIs.

This, to us, highlighted two core problems:

  1. Tool Discovery — It clearly wasn’t as easy for individuals within the organization to discover tools others had created, and therefore …
  2. Waste — As an internal tooling team, we’re not against people making their own tools and scripts — we’re all for it! But while Real Hackers™️ make their own implementations of things for fun, that’s not always the best use of company resources (though obviously implementing better versions to reduce future time/toil is, so there’s clearly a balance)

Overall, we saw a need for scripts and CLIs to be more easily discovered and shared. Additionally, we started to see a growing number of internal developer needs that could be solved by Yet Another™️ CLI; needs like creating/managing Kubernetes environments with sensible company defaults, interacting with CI, and more.

https://xkcd.com/927/: Works just as well for CLIs as for Standards

Managing the Development to Success: One CLI to Rule them All

We’ve therefore started creating an entirely new CLI, inspired by kubectl and Hashicorp CLIs. The CLI is centered around the theme of plugins — it allows internal users to install, manage and run plugins (scripts or CLIs, written in any language) created by teams throughout the company. We are providing our users a few standard plugins installed by default, so that users can use it without further setup in automated CI pipelines. Further, we are inviting developers throughout the company to implement their own plugin in any language, or retrofit their existing script/CLI with the simple requirements to be included, and provide it to their team and the company with a standard mechanism.

We see three major benefits to this approach:

  1. Discovery/Reusability
  2. Collecting company tooling
  3. Metrics

1. Discoverability/Reusability

As we discussed above, one of the major issues we are trying to address is discoverability of tooling, and ensuring that there is less need for re-implementations. We want to empower our users to collaborate on improving existing tooling, and help them avoid reinventing the wheel.

2. Collecting Company Tooling

At this point, there are a large number of internal company tools that are scattered through our R&D teams — whether it be team scripts to take care of one recurring task, CLIs for internal products, or CLIs to wrap other tools (for instance, a wrapper around Chef or Kubectl to ensure developers use our preferred paths). And, as our R&D organization grows (with AppsFlyer looking to add 300 employees within the year), it’s easy to see that this number will start to grow rapidly.

For developers who would use them, it can be difficult to remember the exact name and usage of the tools, and their utility will drop the harder it is to discover them and use them. By collecting individual tools as plugins within one command, users will be able to see all of their AppsFlyer plugins and easily use them as needed.

3. Metrics

As a data company, we at AppsFlyer are obsessed with measuring our tools, ensuring that they are working as they should be (we go to the Synagogue of Metrics as opposed to the Church of Graphs, but they’re pretty similar). By creating this plugin ecosystem, we can provide free alerts to plugin authors if there are any issues with their tools, and make sure that our internal tooling is resilient for users. Moreover, by creating a default template for future plugins, we can deeply embed metrics into future plugins and give our developers tracing and metrics out of the box.

CLI Design Principles: Nothing Else Matters

In rewriting the CLI, we had to consider which principles were our top priorities. There are many great articles on factors to consider when creating a CLI (a particular favorite of mine is Jeff Dickey’s 12 Factor CLI Apps). Three principles in particular we are focusing on are:

  1. Feedback Cycles
  2. Clarity/Simplicity
  3. Stability and Versioning

1. Instituting Feedback Cycles

As a team, we realized that our products would only be helpful if we ensured that we were getting consistent and fast feedback on our products. As such, we instituted a weekly meeting with around 10 so-called “champions”, representatives of different teams and organizations throughout R&D, to show what we had done, and get feedback on what we should be doing next to best address their needs and concerns.

This system has proven pivotal to all of our products to ensure that we are solving the right problems. Particularly in the context of the CLI, it has enabled us to really focus on making sure the process is as simple as possible for users to implement, and the plugins we are working on are solving the most user needs that we can. We are also using this to validate that developers from all over the company are interested in contributing plugins, and finding beta users.

2. Extensibility

Ultimately, our tool will only be valuable if teams throughout the company use it, and contribute plugins to it. To that end, we are trying to ensure that the integration process is as simple as possible.

Our main requirement to integrate a plugin, is that the CLIs support a `deploy` command, to print a YAML detailing the commands it presents, among other pieces of metadata. To ease adoption, we’ve exposed a NewDeployCommand through an SDK, so that Go developers (using the cobra library) can add a config subcommand with one line:

Additionally, we’ll be supporting a base plugin repo that internal Go CLI developers can use as a starting point for their CLIs (with metrics and the configuration command built in), and will explore doing the same for other languages as there is a need.

3. Stability

The CLI, and the plugins that we ourselves support, should be stable. Simply put, they should be production ready, with testing, monitoring and support. One element that we are practicing and encouraging is semantic versioning, to ensure we can provide stable baselines and performance.

Wrapping Up

As we look into the next phase of our internal tooling, we are happy to be spending time upgrading our entire company toolchain, using our af-cli as a centerpiece and a delivery mechanism. By ensuring we have fast feedback loops, high extensibility and stability, we hope this will be a transformative project within the organization, and inspire internal stabilization and exploration.

If you found this project, or our mission of providing the best in class self service solutions to the entire R&D organization interesting, we’re hiring!

--

--