For a few years, I’m working with my own CLI for AWS. The code was quite a mess. Recently, I decided to start from scratch. Giving you access to my highly opinionated commands and (in)sane defaults, doesn’t make sense. Tell you how to build your own CLI with some tips & tricks, that might be valuable. At the end of this blog post, you can do things like:
Creating S3 objects:
$ aws s3a make —-content-type 'text/html' s3://mybucket/myfile.html "Hello World"
Quickly view what is in an S3 file:
$ aws s3a cat s3://mybucket/myfile.txt
Hello World
Use default commands that are available in aws s3
:
$ aws s3a ls
Bucket1
Bucket2
Bucket3
Make an alias for cloudformation
and create a shortcut to list active stacks:
$ aws cfn list
Stack1
Stack2
Stack3
And of course here also the defaults still work:
$ aws cfn list-stacks
{
...
}
About the tools we’re using:
This blog post is divided in 3 steps.
aws
CLI alias and shortcuts.aws
commands to our new CLI.brew install pyenv
is highly recommended)aws sts get-caller-identity
should work)I add the following content to ~/.aws/cli/alias.
|
|
I open a new terminal and try out my shortcuts.
$ aws whoami
{
"UserId": "AHJHEFSEFSDLKVJERWEFDS:session",
"Account": "111111222222",
"Arn": "arn:aws:sts::111111222222:assumed-role/admin/session"
}
$ aws myip
82.53.64.3
$ aws cfn list-stacks
{
...
}
I start with this code structure. In the future my command will be mycli
. It doesn’t really matter what you use, as long it is not being used by other tools. In step 3, we will replace mycli
for aws
.
The __init__.py
files are empty, but make it possible to properly import all modules.
├── README.md
├── mycli
│ ├── __init__.py
│ ├── commands
│ │ ├── __init__.py
│ │ └── s3.py
│ ├── helper.py
│ └── main.py
└── setup.py
I create a minimal setup.py. If you ever going to publish your cli, you should fill in all details. Also add all the dependencies. MyCLI is going to use boto3, cfn linters, config parser etc. Extend this list with all the tools you might add to your cli.
|
|
Here we merge all sub commands into the main cli. If you want to add another command library. Replace “command” for the actual short description you would like to use. For example: cfn, iam, sts or ecs.
from mycli.commands.command import command
.cli.add_command(command)
after the last added command.
|
|
Btw, there is probably a way to loop through all the modules and import them. For me it’s not a big deal and it doesn’t violate any code style standards. In the example it’s only one import, in my private cli there are more of course.
In the helper.py, I’ll collect all functions that are used multiple times in my application. A lot of things are presented as a dictionary and with pprint(obj)
I can easily convert that to json.
|
|
I’ll give you the s3 as an example. AWS already created a few shortcuts for working with S3. The features it lacks of and I want to use quite often:
mycli s3 cat s3://mybucket/myfile.txt
. It shows the content of a file. Don’t do this with large files.mycli s3 make s3://mybucket/myfile.txt "Hello World"
. It creates an S3 object with some content in it.
|
|
Locate the setup.py and in that folder execute the following command. This way you can easily make changes to you awscli, that are immediately available. The installation created symbolic links to your project folder. So after moving the folder, maybe reinstall.
$ pip install -e .
Installing...
Now it’s installed I can see a few help files.
$ mycli --help
Usage: mycli [OPTIONS] COMMAND [ARGS]...
Options:
-h, --help Show this message and exit.
Commands:
s3
$ mycli s3 --help
Usage: mycli s3 [OPTIONS] COMMAND [ARGS]...
Options:
-h, --help Show this message and exit.
Commands:
cat
make
$ mycli s3 make --help
Usage: mycli s3 make [OPTIONS] S3PATH [CONTENT]
Options:
--acl TEXT Access Control List: private | public-read
--content-type TEXT Content type of the file, like text/plain or text/html
-h, --help Show this message and exit.
Now I want to let my brand new cli to work with the aws cli. I can use aliases here as well. I already have a shortcut for cfn (cloudformation), now I’ll create one for s3 as well: s3a. Just because something shorter than “s3” will is not practical. And s3a makes sense, the a standing for alias. Now aws s3
still works, aws s3api
too, and aws s3a
which is an alias for aws s3
and mycli.
|
|
Now I have both the original mycli available, and the alias aws s3a
. It sends the commands to mycli
or to aws s3
.
$ mycli s3 cat s3://mybucket/myfile.txt
$ aws s3a cat s3://mybucket/myfile.txt
$ mycli s3 make s3://mybucket/myfile.txt "Hello World"
$ aws s3a make s3://mybucket/myfile.txt "Hello World"
$ aws s3a ls
Bucket1
Bucket2
I’ll add also these shortcuts and sane defaults for CloudFormation. I created my own cli for cloudformation as well, but it’s Work in Progress, so not to be shared.
|
|
$ aws cfn launch teststack template.yml
...
$ aws cfn list
------------------------------------------------------------------------
| ListStacks |
+-------------+-------------------+------------------------------------+
| StackName | StackStatus | UpdateTime |
+-------------+-------------------+------------------------------------+
| teststack | UPDATE_COMPLETE | 2020-04-08T18:49:08.717000+00:00 |
+-------------+-------------------+------------------------------------+
$ aws cfn resources
...
$ aws cfn events
...
$ aws cfn outputs
...
$ aws cfn delete teststack
...
$ aws s3a make --help
Usage: mycli s3 make [OPTIONS] S3PATH [CONTENT]
Options:
--acl TEXT Access Control List: private | public-read
--content-type TEXT Content type of the file, like text/plain or text/html
-h, --help Show this message and exit.
...
We have learned how to extend the aws cli with some shortcuts, sane defaults, and our own mycli based on Python and Click.
I’m working on an aws cfn deploy
command in python. This command validates the template for errors (cfn-lint), tiggers the deployment using changesets (with confirmation), and shows all events while the stack is created or updated. Parameters like tags and stack parameters are key value in json objects.
I also have a command aws login cli <profile>
, that finds the specified profile, asks for my MFA token, assumes a role and write the temporary credentials in the specified --profile
or default profile. This profile can easily be used by other applications without storing the temporary credentials in arbitrary locations or pass as environment variables. It also works well with docker containers.
-Martijn
I just discovered some strange behaviour when you’re trying to use a command to login. My idea was to build something like this: aws login cli <source> --profile <target>
. It finds the role information in
Photo by Juan Gomez on Unsplash