Deploy xkcdpass as a service
In this blog post, we will extend our previous xkcdpass package by
developping an API around xkcdpass. The goal is to generate a new password on each API request. The API will be in plain text for
the moment.
Here is our API code, we are using uvicorn as a web server and xkcdpass package.
let's write this in a xkcdpassapi.py:
import os
import uvicorn
from xkcdpass import xkcd_password as xp
wordfile = xp.locate_wordfile()
mywords = xp.generate_wordlist(wordfile=wordfile, min_length=5, max_length=8)
def main():
uvicorn.run("xkcdpassapi:app",
host=os.environ.get("HOST", "0.0.0.0"),
port=int(os.environ.get("PORT", "5000")),
log_level="info")
async def app(scope, receive, send):
assert scope['type'] == 'http'
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
[b'content-type', b'text/plain'],
],
})
await send({
'type': 'http.response.body',
'body': xp.generate_xkcdpassword(mywords).encode(),
})
if __name__ == "__main__":
main()
The app server will be listening on 0.0.0.0:5000 by default, but can be overriden by HOST and PORT environment variables.
Next, let's create a Dockerfile to package the app:
FROM python:3.9-alpine AS BUILD
RUN apk --update add gcc build-base
RUN mkdir -p /xkcdpass/dist
RUN pip install shiv
RUN pip install xkcdpass==1.17.4 uvicorn[standard] --target /xkcdpass/dist
ADD xkcdpassapi.py /xkcdpass/dist
RUN cd /xkcdpass && shiv -o xkcdpass.pyz --site-packages dist -e xkcdpassapi:main --compressed
FROM python:3.9-alpine
COPY --from=BUILD /xkcdpass/xkcdpass.pyz /bin/
CMD xkcdpass.pyz
Again we're using multi-stage build:
- First step we install dependencies and package eht whole app and dependencies in a shiv
xkcdpass.pyz
- Then we load
xkcdpass.pyz
in the final image
Next, let's build the container:
podman build -t blog/xkcdpass-api .
That should give the following output:
STEP 1: FROM python:3.9-alpine AS BUILD
✔ docker.io/library/python:3.9-alpine
Getting image source signatures
Copying blob 1d917aee477c done
Copying blob ca3cd42a7c95 done
Copying blob 07f5f7d4c6ff done
Copying blob b429b6305de5 done
Copying blob 747e39ff2433 done
Copying config 62acc19195 done
Writing manifest to image destination
Storing signatures
STEP 2: RUN apk --update add gcc build-base
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.13/community/x86_64/APKINDEX.tar.gz
(1/20) Installing libgcc (10.2.1_pre1-r3)
(2/20) Installing libstdc++ (10.2.1_pre1-r3)
(3/20) Installing binutils (2.35.1-r1)
(4/20) Installing libmagic (5.39-r0)
(5/20) Installing file (5.39-r0)
(6/20) Installing libgomp (10.2.1_pre1-r3)
(7/20) Installing libatomic (10.2.1_pre1-r3)
(8/20) Installing libgphobos (10.2.1_pre1-r3)
(9/20) Installing gmp (6.2.1-r0)
(10/20) Installing isl22 (0.22-r0)
(11/20) Installing mpfr4 (4.1.0-r0)
(12/20) Installing mpc1 (1.2.0-r0)
(13/20) Installing gcc (10.2.1_pre1-r3)
(14/20) Installing musl-dev (1.2.2-r0)
(15/20) Installing libc-dev (0.7.2-r3)
(16/20) Installing g++ (10.2.1_pre1-r3)
(17/20) Installing make (4.3-r0)
(18/20) Installing fortify-headers (1.1-r0)
(19/20) Installing patch (2.7.6-r6)
(20/20) Installing build-base (0.5-r2)
Executing busybox-1.32.1-r5.trigger
OK: 204 MiB in 56 packages
--> b0ce731f613
STEP 3: RUN mkdir -p /xkcdpass/dist
--> 08ec71cd774
STEP 4: RUN pip install shiv
Collecting shiv
Downloading shiv-0.4.0-py2.py3-none-any.whl (19 kB)
Requirement already satisfied: pip>=9.0.3 in /usr/local/lib/python3.9/site-packages (from shiv) (21.0.1)
Collecting click!=7.0,>=6.7
Downloading click-7.1.2-py2.py3-none-any.whl (82 kB)
Requirement already satisfied: setuptools in /usr/local/lib/python3.9/site-packages (from shiv) (54.2.0)
Installing collected packages: click, shiv
Successfully installed click-7.1.2 shiv-0.4.0
--> d47f8bde5e4
STEP 5: RUN pip install xkcdpass==1.17.4 uvicorn[standard] --target /xkcdpass/dist
Collecting xkcdpass==1.17.4
Downloading xkcdpass-1.17.4.tar.gz (2.3 MB)
Collecting uvicorn[standard]
Downloading uvicorn-0.13.4-py3-none-any.whl (46 kB)
Collecting h11>=0.8
Downloading h11-0.12.0-py3-none-any.whl (54 kB)
Collecting click==7.*
Using cached click-7.1.2-py2.py3-none-any.whl (82 kB)
Collecting watchgod>=0.6
Downloading watchgod-0.7-py3-none-any.whl (11 kB)
Collecting PyYAML>=5.1
Downloading PyYAML-5.4.1.tar.gz (175 kB)
Installing build dependencies: started
Installing build dependencies: finished with status 'done'
Getting requirements to build wheel: started
Getting requirements to build wheel: finished with status 'done'
Preparing wheel metadata: started
Preparing wheel metadata: finished with status 'done'
Collecting uvloop!=0.15.0,!=0.15.1,>=0.14.0
Downloading uvloop-0.15.2.tar.gz (2.1 MB)
Collecting httptools==0.1.*
Downloading httptools-0.1.1.tar.gz (106 kB)
Collecting websockets==8.*
Downloading websockets-8.1.tar.gz (58 kB)
Collecting python-dotenv>=0.13
Downloading python_dotenv-0.17.0-py2.py3-none-any.whl (18 kB)
Building wheels for collected packages: xkcdpass, httptools, websockets, PyYAML, uvloop
Building wheel for xkcdpass (setup.py): started
Building wheel for xkcdpass (setup.py): finished with status 'done'
Created wheel for xkcdpass: filename=xkcdpass-1.17.4-py3-none-any.whl size=2325695 sha256=7553f862b63b119c5ea7ca356427293d790521c4b7df00d1d382f170dd7917be
Stored in directory: /root/.cache/pip/wheels/5c/59/b1/f022dc79b4977e1da978c895469b6796a98c49ce98e792e050
Building wheel for httptools (setup.py): started
Building wheel for httptools (setup.py): finished with status 'done'
Created wheel for httptools: filename=httptools-0.1.1-cp39-cp39-linux_x86_64.whl size=105051 sha256=c01116d5c67acd3beee02a26ebd9646dc8603ff0f60692eda12d6aeb940f096d
Stored in directory: /root/.cache/pip/wheels/bf/91/65/284ee85517eb6c733119a30257c9c535f795034065a3911b3f
Building wheel for websockets (setup.py): started
Building wheel for websockets (setup.py): finished with status 'done'
Created wheel for websockets: filename=websockets-8.1-cp39-cp39-linux_x86_64.whl size=65602 sha256=0409d4b1f1239b456369479f8225202adebe494e6e76d5ebe17d8836af22747b
Stored in directory: /root/.cache/pip/wheels/d8/b9/a0/b97b211aeda2ebd6ac2e43fc300d308dbf1f9df520ed390cae
Building wheel for PyYAML (PEP 517): started
Building wheel for PyYAML (PEP 517): finished with status 'done'
Created wheel for PyYAML: filename=PyYAML-5.4.1-cp39-cp39-linux_x86_64.whl size=45641 sha256=3a2f31bc4826766d19cdb6e7b3e87a79e2b4862fe576cbd23aa3c82be34b26fa
Stored in directory: /root/.cache/pip/wheels/b7/a5/c4/504d913c2a55bb09c607541578ec5f844d1ff33467abe93ba5
Building wheel for uvloop (setup.py): started
Building wheel for uvloop (setup.py): still running...
Building wheel for uvloop (setup.py): finished with status 'done'
Created wheel for uvloop: filename=uvloop-0.15.2-cp39-cp39-linux_x86_64.whl size=1444048 sha256=ff338657fbe123cf6de64d7c71de53fb373155cd82ead295b590eb3170a1a655
Stored in directory: /root/.cache/pip/wheels/e2/1a/63/bd18a6050fe7d4a9b180c13874e42a1d2d56f9feca2fc0cd2c
Successfully built xkcdpass httptools websockets PyYAML uvloop
Installing collected packages: h11, click, websockets, watchgod, uvloop, uvicorn, PyYAML, python-dotenv, httptools, xkcdpass
Successfully installed PyYAML-5.4.1 click-7.1.2 h11-0.12.0 httptools-0.1.1 python-dotenv-0.17.0 uvicorn-0.13.4 uvloop-0.15.2 watchgod-0.7 websockets-8.1 xkcdpass-1.17.4
--> 3bd276e1404
STEP 6: ADD xkcdpassapi.py /xkcdpass/dist
--> 649e6c6e3fd
STEP 7: RUN cd /xkcdpass && shiv -o xkcdpass.pyz --site-packages dist -e xkcdpassapi:main --compressed
--> de6b9414bba
STEP 8: FROM python:3.9-alpine
STEP 9: COPY --from=BUILD /xkcdpass/xkcdpass.pyz /bin/
--> 629fc85d3ef
STEP 10: CMD xkcdpass.pyz
STEP 11: COMMIT blog/xkcdpass-api
--> ef4c287bcc4
ef4c287bcc406e1650c49b4260718f2b151f202155d36dc6017f9d56bdffe5c0
Try the command with:
podman run -it blog/xkcdpass-api
Gives the following output:
INFO: Started server process [1]
INFO: Waiting for application startup.
INFO: ASGI 'lifespan' protocol appears unsupported.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:5000 (Press CTRL+C to quit)
INFO: 10.0.2.100:50252 - "GET / HTTP/1.1" 200 OK
Then
curl http://localhost:5000
heroku login
heroku container:login
heroku create
heroku container:push web
heroku container:release web
heroku open