Amsterdam Standard
Published in

Amsterdam Standard

Yake — Yet Another maKe clonE

Let us invite you to the CLI (command-line interface) world. How often do you need an alias for some long commands? How often do you need to duplicate some (sometimes the same) bash commands from one project to another? How often do you copy & paste long command lines and edit only one word before execution?

Do these questions sound familiar? It was a huge challenge for us too!

The background

When we have started using Docker as a stack for our project, we ran into some CLI issues. Let’s say we have a Docker Container called webapp. It’s a simple web server application, with some Backend stuff. To execute basic commands on the Backend, like database migration or create a new user, we have to reach the Backend CLI first.

Step 1: Log in to the container

$ docker-compose exec webapp bash

Step 2: Execute a CLI command

$ docker-compose exec webapp bash
root@1802asdf6d:/# ./manage.py migrate

Step 3: We can improve it a bit by inserting a full command directly to the “exec”

docker-compose exec webapp ./manage.py migrate

Not bad, right?

Step 4: We can also have a CLI tool inside the Container with some extra params required to make it work

docker-compose exec webapp php composer.phar show monolog/monolog

In this case, it would be great to have at least a webapp alias, to use:

webapp php composer.phar show monolog/monolog

But how about getting one step further and create one more alias called composer, to be able to use:

composer show monolog/monolog

Instead of the full command?

There is a nice solution for that: creating a Bash “aliases” and add them globally. This way you can reuse them for each project. But what happens if the container has a different name? For example app instead of webapp?

There is an opportunity to add a Bash alias locally inside the project. In this case, you have to be sure you “import” such aliases each time you want to use them. But as we know, It’s not something that developers like to do.

Here you might have said that there are some dedicated tools already exist. For example, Make?

Make — see daylight

Make is a dedicated CLI tool to compile applications from the source code. It’s usually used for the C language projects. It has a local (per project) text file called “Makefile” with all the commands required to build the app (aliases). This is actually all that we need!

Let’s have a look at Makefile sample (please don’t focus on not so clear code lines like “%: ; @:”):

Makefile

We can execute a short command using:

make composer CMD=”show monolog/monolog”

And it will work.

What are the issues with Make?

First of all, Make is a tool to build a low-level language code. It has some features and limitations for this job like tasks auto execution feature (next command on the list is executed when the previous one succeeded) or lack of inserting extra command to the task during the execution (see a “CMD” hack), and a few more drawbacks.

Moreover, it’s not always installed by default in the system. You have no extra advantage to using it because it requires installing additional software.

And the final disadvantage: the Makefile structure is not intuitive. Using it isn’t a pleasure, as you need to overwrite a CMD each time.

Are there more ways to work with this challenge? — Yes.

NodeJS way

NodeJS becomes more and more popular and the JS developers run into the same “aliases” issues like us. How do they solve it?

packages.json file has a “scripts” section where we can define a custom “aliases” to the longer commands. Moreover, it supports console auto-completion, extra params injection and task preview feature. It’s actually all we want to have.

package.json

But, there are some difficulties. You can’t just handle a NodeJS tool to manage Docker-based projects. One of the reasons could be no NodeJS environment in some projects. Furthermore, “scripts” was developed to help with NodeJS environment, not the Docker one. Mixing both of them in the same file may be hard to maintain.

Do we have a solution for this challenge? — and Yes again!

It’s time for Yake

As we didn’t find any worth solution through the existing ones, we decided to create a separate tool — Yake. First of all to manage commands for Docker-based projects, but also for all projects where any “aliases” are needed. Yake is a free to use, open source Perl & Bash script, developed by Amsterdam Standard.

We’ve combined Make features, package.json flexibility and implemented it into the one bash-based CLI environment.

Yake is a shell script, installed globally, which loads a local directory’s Yakefile YAML file, where we can define all the aliases. The flow is the same like on Make.

Yake file

Once the Yake is installed, you can use a yakecommand to execute local-defined aliases.

The most important features:

  • Recursive support (one command can run the other one inside)
  • Full native bash support, including CLI interactive and I/O (tasks are executed directly on the bash environment, no proxy or capsulation)
  • Variables support (commands can be modified dynamically using pre-defined ENVs)
  • Auto-completion for Bash and Zsh
  • Able to debug (show) command before execution

Let’s have a look at the Docker example.

We have a webapp command defined with $NAME ENV (webapp value by default). To enter the Container’s bash shell, you can run:

yake webapp bash

You can also overwrite the “$NAME” ENV by custom Container name:

yake NAME=webapp-dev webapp bash

Thanks to recursive commands and native Bash support, you can run the CLI Backend tool with all necessary params:

yake manage user:create — role=”admin” — force

All the bash error response codes and I/O operations are supported.

Yake — behind the scenes

Yake is actually 2 separate scripts: Perl Core and Bash runner.

Perl Core is responsible for loading a local YAML file and create a full bash command. It reads all the aliases and processes them. Array commands are merged, all the internal VARs are injecting. It has also some HOWTO knowledge included in the source code.

Bash runner passes the user’s command to the Core, and run it or display the result.

Why only Bash or only Perl is not used? Unfortunately, Perl doesn’t support a native Bash script execution (e.g. I/O interaction). But Bash doesn’t go well with YAML files and processing the array of chars.

We also tried to use Go-lang, but it doesn’t support a native Bash command execution, along with all other non-Bash languages.

Installation

The only requirement to install Yake is to have Perl installed. For all popular OS Perl is installed by default. In some cases (you will be notified by Yake installer), cpanm (cpanminus) package could be required to install:

curl -L https://cpanmin.us | perl — App::cpanminus

Then you can install Yake:

curl -L https://yake.amsdard.io/install.sh | sudo -E bash

To summarize

It was a pleasure for us to develop Yake. We are using it now for all our projects and we enjoy it. Managing project aliases isn’t a nightmare anymore. What are we going to do next? We’ve made the Yake public and currently, we are collecting feedback from users to improve it as much as possible. Please, feel free to leave yours in the comment field below! Further, we are planning to deploy Yake into the Linux repository and Brew package to provide users the simplest way of installing it.

To find out some extra examples please check the Yake official website:

Enjoy Yake along with us!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Amsterdam Standard

The technology partner for growing businesses. We help companies to build software, websites, and apps. Visit amsterdamstandard.com for more information.