├── .gitignore ├── README.md ├── dockerfdw ├── __init__.py └── wrappers │ ├── __init__.py │ ├── base.py │ ├── containers.py │ └── images.py ├── fig.yml ├── pg ├── Dockerfile └── run.sh ├── setup.py └── setup.sql /.gitignore: -------------------------------------------------------------------------------- 1 | *swp 2 | *pyc 3 | *dockerfdw*egg* 4 | build/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dockerfdw 2 | ========= 3 | 4 | PostgreSQL Foreign Data Wrapper for Docker! 5 | 6 | Requirements: 7 | ------------- 8 | 9 | * PostgreSQL 9.2+ 10 | * Multicorn 1.0.4 11 | * Fig (optional) 12 | 13 | Quick Setup 14 | ----------- 15 | 16 | _note_: `fig.yml` uses volume mapping, so the host must be able to volume share 17 | to the docker host. 18 | 19 | ```bash 20 | $ fig build pg 21 | $ fig up -d pg 22 | $ fig logs 23 | ``` 24 | 25 | 26 | _note_: password is `docker` 27 | 28 | ```bash 29 | $ psql -h 127.0.0.1 -p 5432 -U docker -W docker 30 | psql (9.3.5) 31 | Password for user docker: 32 | SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256) 33 | Type "help" for help. 34 | 35 | docker=# select id, name, pid from docker_containers; 36 | id | name | pid 37 | ------------------------------------------------------------------+----------------+------- 38 | 56995a1e1ebc4a56ffd190db7d09ee526d49e265ae38aa61f3d2d5cafa0add01 | /code_pg_1 | 13076 39 | 0bb24aaa89af5f3cc463b29f7a4e613e0268a2f84c2d3be38941f7238603cd08 | /code_pg_run_5 | 40 | (7 rows) 41 | ``` 42 | -------------------------------------------------------------------------------- /dockerfdw/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1" 2 | -------------------------------------------------------------------------------- /dockerfdw/wrappers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paultag/dockerfdw/a67efe4d29f758aee8a1ab6f7312c593a5fb1daa/dockerfdw/wrappers/__init__.py -------------------------------------------------------------------------------- /dockerfdw/wrappers/base.py: -------------------------------------------------------------------------------- 1 | from multicorn.utils import log_to_postgres 2 | from multicorn import ForeignDataWrapper 3 | import docker 4 | import json 5 | 6 | 7 | def nully(obj): 8 | return obj if obj else None 9 | 10 | 11 | class APIProxy(object): 12 | def __init__(self, client): 13 | self._cache = {} 14 | self.client = client 15 | 16 | def handle(self, id_): 17 | raise NotImplementedError 18 | 19 | def list(self): 20 | raise NotImplementedError 21 | 22 | def get(self, id_): 23 | if id_ in self._cache: 24 | return self._cache[id_] 25 | ret = self.handle(id_) 26 | # log_to_postgres(json.dumps(ret, sort_keys=True, indent=4)) 27 | self._cache[id_] = ret 28 | return ret 29 | 30 | 31 | class BaseDockerFdw(ForeignDataWrapper): 32 | proxy_type = None 33 | proxy = None 34 | 35 | def __init__(self, fdw_options, fdw_columns): 36 | super(BaseDockerFdw, self).__init__(fdw_options, fdw_columns) 37 | self.host = fdw_options['host'] 38 | self.client = docker.Client(base_url=self.host, version='1.12') 39 | 40 | if self.proxy_type: 41 | self.proxy = self.proxy_type(self.client) 42 | -------------------------------------------------------------------------------- /dockerfdw/wrappers/containers.py: -------------------------------------------------------------------------------- 1 | from multicorn.utils import log_to_postgres 2 | from logging import ERROR, DEBUG, INFO, WARNING 3 | from .base import BaseDockerFdw, APIProxy, nully 4 | 5 | import datetime 6 | 7 | 8 | class ContainerProxy(APIProxy): 9 | def list(self): 10 | self._containers = self.client.containers(all=True) 11 | self._container_map = {x['Id']: x for x in self._containers} 12 | return self._containers 13 | 14 | def handle(self, id_): 15 | ret = self.client.inspect_container(id_) 16 | ret['Names'] = self._container_map[id_]['Names'] 17 | return ret 18 | 19 | 20 | class ContainerFdw(BaseDockerFdw): 21 | proxy_type = ContainerProxy 22 | rowid_column = 'id' 23 | 24 | spec = { 25 | "id": lambda x: x['Id'], 26 | "image": lambda x: x['Image'], 27 | "name": lambda x: x['Name'], 28 | "names": lambda x: x['Names'], 29 | "command": lambda x: x['Config']['Cmd'], 30 | "privileged": lambda x: x['HostConfig']['Privileged'], 31 | "ip": lambda x: x['NetworkSettings']['IPAddress'], 32 | "bridge": lambda x: x['NetworkSettings']['Bridge'], 33 | "running": lambda x: x['State']['Running'], 34 | "pid": lambda x: x['State']['Pid'], 35 | "exit_code": lambda x: x['State']['ExitCode'], 36 | } 37 | 38 | def delete(self, id_): 39 | self.client.stop(id_, timeout=10) 40 | self.client.remove_container(id_) 41 | 42 | def insert(self, new_values): 43 | required = ['image'] 44 | blacklist = ['bridge', 'ip', 'pid', 'exit_code', 'id', 45 | 'running', 'names'] 46 | default = {'privileged': False, 47 | 'command': None, 48 | 'name': None,} 49 | config = {} 50 | 51 | for el in blacklist: 52 | if el in new_values: 53 | if new_values[el] is not None: 54 | raise ValueError("Error: Can not handle column `%s`" % (el)) 55 | new_values.pop(el) 56 | 57 | for el in required: 58 | if el not in new_values: 59 | raise ValueError("Required column `%s' missing." % (el)) 60 | config[el] = new_values.pop(el) 61 | 62 | for el in default: 63 | if el in new_values: 64 | config[el] = new_values.pop(el) 65 | else: 66 | config[el] = default[el] 67 | 68 | privileged = config.pop("privileged") 69 | client = self.client.create_container( 70 | stdin_open=True, 71 | tty=True, 72 | detach=False, 73 | **config 74 | ) 75 | id_ = client.pop("Id") 76 | self.client.start(id_, privileged=privileged) 77 | 78 | assert new_values == {} 79 | return {"id": id_} 80 | 81 | def execute(self, quals, columns): 82 | for container in self.proxy.list(): 83 | yield {x: nully(self.spec[x](self.proxy.get(container['Id']))) 84 | for x in columns} 85 | -------------------------------------------------------------------------------- /dockerfdw/wrappers/images.py: -------------------------------------------------------------------------------- 1 | from multicorn.utils import log_to_postgres 2 | from logging import ERROR, DEBUG, INFO, WARNING 3 | from .base import BaseDockerFdw, APIProxy, nully 4 | 5 | import datetime 6 | import copy 7 | 8 | 9 | class ImageProxy(APIProxy): 10 | def handle(self, id_): 11 | ret = self.client.inspect_image(id_) 12 | ret['RepoTags'] = self._image_map[id_]['RepoTags'] 13 | return ret 14 | 15 | def list(self): 16 | self._images = self.client.images() 17 | self._image_map = {x['Id']: x for x in self._images} 18 | return self._images 19 | 20 | 21 | class ImageFdw(BaseDockerFdw): 22 | proxy_type = ImageProxy 23 | 24 | spec = { 25 | "id": lambda x: x['Id'], 26 | "comment": lambda x: x['Comment'], 27 | "author": lambda x: x['Author'], 28 | "parent": lambda x: x['Parent'], 29 | "tags": lambda x: x['RepoTags'], 30 | "architecture": lambda x: x['Architecture'], 31 | } 32 | 33 | def execute(self, quals, columns): 34 | for image in self.proxy.list(): 35 | yield {x: nully(self.spec[x](self.proxy.get(image['Id']))) 36 | for x in columns} 37 | -------------------------------------------------------------------------------- /fig.yml: -------------------------------------------------------------------------------- 1 | pg: 2 | build: pg 3 | ports: 4 | - 5432:5432 5 | volumes: 6 | - .:/src 7 | - /var/run/docker.sock:/run/docker.sock 8 | environment: 9 | DB_USER: docker 10 | DB_PASS: docker 11 | DB_NAME: docker 12 | -------------------------------------------------------------------------------- /pg/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM sherzberg/multicorn:pg-upgrade 2 | 3 | ADD run.sh /run.sh 4 | 5 | CMD ["/run.sh"] 6 | -------------------------------------------------------------------------------- /pg/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # little hack to bypass permissions issues 4 | chmod 777 /run/docker.sock 5 | 6 | /start.sh 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | from dockerfdw import __version__ 5 | 6 | long_description = "Docker FDW" 7 | 8 | setup( 9 | name="dockerfdw", 10 | version=__version__, 11 | packages=[ 12 | 'dockerfdw', 13 | 'dockerfdw.wrappers', 14 | ], 15 | install_requires=[ 16 | 'docker-py', 17 | ], 18 | author="Paul Tagliamonte", 19 | author_email="paultag@debian.org", 20 | long_description=long_description, 21 | description='does some stuff with things & stuff', 22 | license="Expat", 23 | url="", 24 | platforms=['any'] 25 | ) 26 | -------------------------------------------------------------------------------- /setup.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION multicorn; 2 | 3 | DROP FOREIGN TABLE IF EXISTS docker_containers; 4 | DROP FOREIGN TABLE IF EXISTS docker_images; 5 | 6 | DROP SERVER IF EXISTS docker_containers; 7 | DROP SERVER IF EXISTS docker_images; 8 | 9 | 10 | CREATE SERVER docker_containers FOREIGN DATA WRAPPER multicorn options ( 11 | wrapper 'dockerfdw.wrappers.containers.ContainerFdw'); 12 | 13 | CREATE SERVER docker_image FOREIGN DATA WRAPPER multicorn options ( 14 | wrapper 'dockerfdw.wrappers.images.ImageFdw'); 15 | 16 | 17 | CREATE foreign table docker_containers ( 18 | "id" TEXT, 19 | "image" TEXT, 20 | "name" TEXT, 21 | "names" TEXT[], 22 | "privileged" BOOLEAN, 23 | "ip" TEXT, 24 | "bridge" TEXT, 25 | "running" BOOLEAN, 26 | "pid" INT, 27 | "exit_code" INT, 28 | "command" TEXT[] 29 | ) server docker_containers options ( 30 | host 'unix:///run/docker.sock' 31 | ); 32 | 33 | 34 | CREATE foreign table docker_images ( 35 | "id" TEXT, 36 | "architecture" TEXT, 37 | "author" TEXT, 38 | "comment" TEXT, 39 | "parent" TEXT, 40 | "tags" TEXT[] 41 | ) server docker_image options ( 42 | host 'unix:///run/docker.sock' 43 | ); 44 | 45 | 46 | -- SELECT docker_containers.ip, docker_containers.names, docker_images.tags 47 | -- FROM docker_containers 48 | -- RIGHT JOIN docker_images 49 | -- ON docker_containers.image=docker_images.id; 50 | 51 | DELETE FROM docker_containers WHERE name='/foo'; 52 | --------------------------------------------------------------------------------