Transform Linux and Windows CLI to REST API w/ Docker & Python

Update 02 dec 2015, some people ask me why I used docker-py instead of REST API, so I added REST API equivalent with curl.

Everyday, I build tools using REST API, SOAP, RPC, HTTP requests on web app … But how can I manage a CLI ?

I started a project called cli2rest. The goal of this application is to wrap any CLI into a simple REST API like apache-libcloud do with different cloud provider.

First idea

  • deploy and manage 2 nodes on GNU/Linux for failover
  • deploy and manage 2 nodes on Windows
  • build an application to wrap local CLI
  • make a powerfull API to manage it

Wow.

Just to make a simple CLI run ?

Wait. Docker have a REST API and can wrap a CLI !

Let’s go

For example, I will use httpie and run http HEAD ahmet2mir.eu remotely.

Create Docker image to wrap httpie CLI

Create a Dockerfile:

Download

    FROM alpine:3.2
    MAINTAINER Ahmet Demir <ahmet2mir+github@gmail.com>

    RUN apk add --update python openssl
    RUN wget //bootstrap.pypa.io/get-pip.py
    RUN python get-pip.py
    RUN pip install --upgrade pip setuptools httpie
    RUN rm -rf /var/cache/apk/*

Build image:

    $ docker build -t ahmet2mir/httpie .

We start a container using a timeout of 120s and execute command inside container:

    $ docker run -d --name httpiedemo ahmet2mir/httpie /bin/sleep 120
    12403c7c03c0b42b1589324d9e53d12123b46195534bae736ca703a41744a6c2

    $ docker exec -t httpiedemo http HEAD ahmet2mir.eu
    HTTP/1.1 200 OK
    Accept-Ranges: bytes
    Connection: keep-alive
    Content-Encoding: gzip
    Content-Length: 1309
    Content-Type: text/html
    Date: Sun, 22 Nov 2015 15:28:13 GMT
    Last-Modified: Tue, 22 Sep 2015 06:25:21 GMT
    Server: Apache/2.4.10 (Debian)
    Vary: Accept-Encoding

    $ docker rm -f httpiedemo

Create REST API

Prerequiste:

  • docker-py, Python package supported by Docker Inc or curl
    $ pip install docker-py
    or
    $ apt install curl
    $ docker -H tcp://127.0.0.1:2375 images

Create a simple script to do the same thing we did with Docker CLI.

Download

    # -*- coding: utf-8 -*-
    from docker import Client

    def cli2rest(cli, image, name, cmd, timeout=120):
        print("===> Create container: %s" % name)
        container = cli.create_container(name=name,
                                         image=image,
                                         command=["/bin/sleep", str(timeout)])
        print("===> Start container: %s" % name)
        cli.start(container=container['Id'])

        print("===> Create exec instance")
        exec_instance = cli.exec_create(container['Id'], cmd=cmd, tty=True)

        print("===> Start exec instance")
        out = cli.exec_start(exec_id=exec_instance['Id'])

        print("===> Remove container")
        cli.remove_container(container=name, force=True)

        return out


    if __name__ == '__main__':
        cli = Client(base_url='tcp://0.0.0.0:2375')
        result = cli2rest(cli=cli,
                          image="ahmet2mir/httpie",
                          name="httpiedemo",
                          cmd=["http","HEAD","ahmet2mir.eu"])

        print("======> Result")
        print(result)

And run!

    $ python cli2rest.py
    ===> Create container: httpiedemo
    ===> Start container: httpiedemo
    ===> Create exec instance
    ===> Start exec instance
    ===> Remove container
    ======> Result

    HTTP/1.1 200 OK
    Accept-Ranges: bytes
    Connection: keep-alive
    Content-Encoding: gzip
    Content-Length: 1309
    Content-Type: text/html
    Date: Sun, 22 Nov 2015 15:30:44 GMT
    Last-Modified: Tue, 22 Sep 2015 06:25:21 GMT
    Server: Apache/2.4.10 (Debian)
    Vary: Accept-Encoding

    ===> Remove container

With curl

    $ curl -X create_container

Make it real on Windows

For this example, download pstools and run psinfo

Download

    FROM debian:jessie
    MAINTAINER Ahmet Demir <ahmet2mir+github@gmail.com>

    RUN dpkg --add-architecture i386 \
        && apt-get update \
        && apt-get install -y wine:i386 unzip

    ADD http://download.sysinternals.com/files/PSTools.zip /tmp/PSTools.zip
    RUN unzip /tmp/PSTools.zip -d /win/

    RUN apt-get clean
    RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

Build

    $ docker build -t ahmet2mir/httpie .

Just replace some values to match the new image in the previous python script

    # -*- coding: utf-8 -*-

    if __name__ == '__main__':
        result = cli2rest(cli=cli,
                          image="ahmet2mir/psinfo",
                          name="psinfodemo",
                          cmd=["wine", "/win/psinfo"])

        print("======> Result")
        print(result)

An run!

    $ python cli2rest.py
    ===> Create container: psinfodemo
    ===> Start container: psinfodemo
    ===> Create exec instance
    ===> Start exec instance
    ===> Remove container
    ======> Result

    PsInfo v1.77 - Local and remote system information viewer
    Copyright (C) 2001-2009 Mark Russinovich
    Sysinternals - www.sysinternals.com

    Querying information for 2df06379fb0f...
    System information for \\2df06379fb0f:
    Uptime:                    0 days 13 hours 33 minutes 19 seconds
    Kernel version:            Microsoft Windows XP, Uniprocessor Free
    Product type:              Professional
    Product version:           5.1
    Service pack:              3
    Kernel build number:       2600
    Registered organization:   
    Registered owner:          
    IE version:                6.0000
    System root:               C:\windows
    Processors:                4
    Processor speed:           3.2 GHz
    Processor type:            Intel(R) Pentium(R) 4 CPU
    Physical memory:           0 MB
    Video driver:              

Conclusion

Also, I only used this for a Linux CLI in production based on Redhat image. I’ve another image for a Windows CLI not deployed in production.

Idempotency wasn’t implemented in this article because it’s depends on your needs. What you will do if the container exists ? Is it currently running something ? Previsouly failed and still up ? Not deleted ?

Now, you can integrate it in your favorite orchestrator and escape the hell :)


Picture by Blake Thornberry under licence CC BY-NC-ND 2.0


If you liked this post, share it on Twitter.