Singularity Global Client: AWS ECR

These sections will detail use of the Amazon ECR (Amazon Elastic Container Registry) client for sregistry, which is a connection to the Docker registry served by Amazon Web Services. Implementation wise, this means that we start with the basic docker client, and tweak it. The tweaks pertain to slight differences in headers and requests that are done by the AWS client.

Why would I want to use this?

Singularity proper will be the best solution if you want to pull and otherwise interact with Docker images. However, the sregistry client can be useful to you if you want to easily do this through Python, such as in a script for scientific programming.

As with Docker Hub The images are built from layers, and the layers that you obtain depend on the uri that you ask for. See the environment. setting for more details.

Getting Started

The AWS Container Registry module uses the awscli python library to help with authentication.

You can install them both like:

pip install sregistry[aws]

If you want to install each yourself:

pip install sregistry
pip install awscli

The next steps we will take are to first set up authentication and other environment variables of interest, and then review the basic usage.

Credentials

You will need to generate a special token from AWS using your IAM login.
Importantly, we are going to be using AWS HTTP Authorization, and this means to get your $AWS_TOKEN you will need to install the AWS Client that we mentioned previously first.

Configuration

If this is the first time you are using it, you will need to configure regions and whatnot.

aws configure

These will not exist until you run the aws configure, and when you run aws configure you will need to give it some access keys for a specific IAM user. Let’s walk through how to generate this user.

Aws Access Key ID and Secret

I was able to get to IAM at this link. I then had to click on the tab to “Create individual IAM users” and then the button to “Manage Users” and then “Add User” to create a new user with Programmatic access.

/sregistry-cli/img/aws-add-user.png

You need to add your user to a group, and select permissions. I chose AmazonEC2ContainerRegistryFullAccess and AmazonEC2ContainerServiceFullAccess because I wasn’t really sure what to choose, but generally wanted all the things! You could very likely create different users with different permissions depending on your use case.

Once you get this, THEN the next screen will give you an “Access Key ID” and secret token, and these are exactly what you want to copy paste into this first field asked for by aws configure. You can also choose a default zone. I chose the one that showed up as default in my Manager Console.

Default Output Format

I chose json, since I knew I’d be interacting with Python and Json. The whole thing is going to look something like:

$ aws configure
AWS Access Key ID [None]: XXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Default region name [None]: us-east-1
Default output format [None]: json

and when you finish, you should have files in your $HOME. The process is writing some credentials to a $HOME/.aws folder. By the end of this process you will likely have two files:

$ tree /home/vanessa/.aws/
/home/vanessa/.aws/
├── config
└── credentials

You can see more details about the various options here

Testing Credentials

It’s important to test your credentials before trying to use them with sregistry. The reason is that a permissions (or related error) should be caught here first.

You should now have enough to generate the token for the client from the command line:

AWS_TOKEN=$(aws ecr get-authorization-token --output text --query 'authorizationData[].authorizationToken')

To get your registry url, I wasn’t sure how to do this either, so I used their docker login command and it shows up as the last item in the call:

aws ecr get-login --no-include-email
...
https://692517157806.dkr.ecr.us-east-1.amazonaws.com

so I set this to a variable too:

AWS_URL=https://692517157806.dkr.ecr.us-east-1.amazonaws.com

Interaction with Repositories

Before trying to use your credentials with the Singularity Registry client, let’s make sure they work! If you selected some kind of different permissions, or otherwise messed something up (hey, it happens!) this command might fail. We would want to identify this is about the setup and not the client itself. This command to list your registries should work.

First, here is a command to test with their client. This likely will return an empty list if you just created the account and its registry:

aws --debug ecr describe-repositories

Next, here is a curl command to use the token (AWS_token) and url we derived (AWS_URL) to ping your endpoint. Since the token expires fairly quickly, I’m going to keep placing the command beside the action we want to do to ensure you copy paste both and get a new token :)

AWS_TOKEN=$(aws ecr get-authorization-token --output text --query 'authorizationData[].authorizationToken')
curl -i -H "Authorization: Basic $AWS_TOKEN" ${AWS_URL}/v2/

If all goes well, you should get a 200 response!

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Tue, 25 Sep 2018 13:01:11 GMT
Docker-Distribution-Api-Version: registry/2.0
Content-Length: 0
Connection: keep-alive

Create a repository

This is pretty annoying, but you have to manually create a repository otherwise you see:

name unknown: The repository with name 'library/busybox' does not exist in the registry with id '692517157806'

Yeah, really. Ug. I went to the repository base page and then created a repository called “library/busybox”. The login (if you haven’t done it yet) looks like this:

$(aws ecr get-login --no-include-email --region us-east-1)

Push a container

Now that we’ve tested the registry endpoint and confirmed we can access it, let’s push a container! How about a tiny one? Yes that sounds good.

docker pull busybox:latest
docker images | grep busybox

and push! You can tag it something else first if you like. Note the first command is removing the https://

AWS_REPO=$(echo $AWS_URL |sed 's/https\?:\/\///')
docker tag busybox:latest ${AWS_REPO}/library/busybox
$(aws ecr get-login --no-include-email --region us-east-1)
...
Login Succeeded

Note that if you don’t have the --no-include-email it’s going to give you a hanging -e that will trigger a bug.

AWS_TOKEN=$(aws ecr get-authorization-token --output text --query 'authorizationData[].authorizationToken')
docker push ${AWS_REPO}/library/busybox
f9d9e4e6e2f0: Pushed 
latest: digest: sha256:5e8e0509e829bb8f990249135a36e81a3ecbe94294e7a185cc14616e5fad96bd size: 527

You should then be able to pull with

docker pull ${AWS_REPO}/library/busybox

It took me almost an hour to get this complete thing working. Not going to comment further on that.

Test with CURL

Now we would want to test pinging our repository with curl. This entire shenanigans should work.

AWS_TOKEN=$(aws ecr get-authorization-token --output text --query 'authorizationData[].authorizationToken')
curl -i -H "Authorization: Basic $AWS_TOKEN" $AWS_URL/v2/library/busybox/tags/list
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Tue, 25 Sep 2018 13:51:09 GMT
Docker-Distribution-Api-Version: registry/2.0
Content-Length: 44
Connection: keep-alive

{"name":"library/busybox","tags":["latest"]}

sregistry Pull

You’ve made it! Now that we have confirmed the endpoint is working, and our container exists there we can talk about interaction with it via sregistry. Let’s first export some of the variables we discussed above, in the format that the sregistry client will find them. Don’t worry - you only need to do this once and never again.

The first thing we need is the ID of your registry. It’s the “number part” of the long URL that we looked at earlier, and it was a part of the $AWS_REPO variable that we used above. We can derive just the id (the numbers) from that. We also need to know the zone your registry is in:

export SREGISTRY_AWS_ID=$(echo $AWS_REPO | cut -d. -f1)
export SREGISTRY_AWS_ZONE=us-east-1

The key and secret are used for authentication, and you derived them above.

export SREGISTRY_AWS_KEY=xxxxxxxxxxx
export SREGISTRY_AWS_SECRET=xxxxxxxxxxxxxxxxx

You’ll only need to do this once, the first time that you use the client.

Python Client

Now let’s try pulling our image! First we will work within python. Note that I’m specifying the aws:// uri to tell the client what I want. If I wanted to do this globally, I would do this before any kind of pull or interaction with the client. For example, here is doing this via a shell:

export SREGISTRY_CLIENT=aws 
$ sregistry shell
[client|aws] [database|sqlite:////home/vanessa/.singularity/sregistry.db]
Python 3.6.4 |Anaconda custom (64-bit)| (default, Jan 16 2018, 18:10:19) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.

$ image = client.pull('library/busybox')
Exploding /usr/local/libexec/singularity/bootstrap-scripts/environment.tar
Exploding /home/vanessa/.singularity/docker/sha256:8c5a7da1afbc602695fcb2cd6445743cec5ff32053ea589ea9bd8773b7068185.tar.gz
[container][new] library/busybox:latest
Success! /home/vanessa/.singularity/shub/library-busybox:latest.simg

Command Line

You can also pull from the command line. Here I’ll show unsetting the SREGISTRY_CLIENT environment variable so you can see how to use the aws:// uri, and also how to not cache the image (and pull to the present working directory with a custom name).

$ unset SREGISTRY_CLIENT
$ sregistry pull --name aws-is-hard.simg --no-cache aws://library/busybox
$ sregistry pull --name aws-is-hard.simg --no-cache aws://library/busybox
[client|aws] [database|sqlite:////home/vanessa/.singularity/sregistry.db]
Exploding /usr/local/libexec/singularity/bootstrap-scripts/environment.tar
Exploding /home/vanessa/.singularity/docker/sha256:8c5a7da1afbc602695fcb2cd6445743cec5ff32053ea589ea9bd8773b7068185.tar.gz
Building image from sandbox: /tmp/tmpl_zzhuba
Building Singularity image...
Singularity container built: aws-is-hard.simg
Cleaning up...
WARNING: Building container as an unprivileged user. If you run this container as root
WARNING: it may be missing some functionality.
WARNING: Building container as an unprivileged user. If you run this container as root
WARNING: it may be missing some functionality.
Success! aws-is-hard.simg

Importantly, does it work?

$ singularity shell aws-is-hard.simg 
Singularity: Invoking an interactive shell within container...

Singularity> 

Yes! Oh thank goodness.