Skip to content

Commit

Permalink
✨ Add Docker plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
julien040 committed Aug 7, 2024
1 parent 658ae54 commit dcaedac
Show file tree
Hide file tree
Showing 12 changed files with 1,166 additions and 0 deletions.
18 changes: 18 additions & 0 deletions plugins/docker/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

# Go template downloaded with gut
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test
*.out
go.work
.gut

# Dev files
*.log
devManifest.*
.init

dist/
32 changes: 32 additions & 0 deletions plugins/docker/.goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

version: 2

before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy

builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
binary: docker
id: anyquery
ldflags: "-s -w"

goarch:
- amd64
- arm64

archives:
- format: binary

changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
13 changes: 13 additions & 0 deletions plugins/docker/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

files := $(wildcard *.go)

all: $(files)
go build -o docker.out $(files)

prod: $(files)
go build -o docker.out -ldflags "-s -w" $(files)

clean:
rm -f docker.out

.PHONY: all clean
119 changes: 119 additions & 0 deletions plugins/docker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Docker plugin

The Docker plugin allows you to query Docker containers, images and networks over SQL.

## Installation

```bash
anyquery install docker
```

## Usage

```sql
-- List all containers
SELECT * FROM docker_containers;

-- List all images from another docker daemon
SELECT * FROM docker_images('tcp://0.0.0.0:2375');

-- List all networks
SELECT * FROM docker_networks;
```

Each table can specify another docker daemon to connect to by passing the connection string as an argument to the table function. You can also set the column `host` like this:

```sql
-- List all containers from another docker daemon
SELECT * FROM docker_containers WHERE host='tcp://0.0.0.0:2375';
```

Finally, the plugin reads the environment variable `DOCKER_HOST` to connect to the docker daemon. If the variable is not set, it will connect to the default docker daemon.

## Schema

### docker_containers

| Column index | Column name | type |
| ------------ | ------------ | ------- |
| 0 | id | TEXT |
| 1 | names | TEXT |
| 2 | image | TEXT |
| 3 | image_id | TEXT |
| 4 | command | TEXT |
| 5 | created_at | TEXT |
| 6 | ports | TEXT |
| 7 | labels | TEXT |
| 8 | size_rw | INTEGER |
| 9 | size_root_fs | INTEGER |
| 10 | state | TEXT |
| 11 | status | TEXT |
| 12 | networks | TEXT |
| 13 | mounts | TEXT |

### docker_container

| Column index | Column name | type |
| ------------ | ---------------- | ------- |
| 0 | id | TEXT |
| 1 | created_at | TEXT |
| 2 | path | TEXT |
| 3 | args | TEXT |
| 4 | container_state | TEXT |
| 5 | image | TEXT |
| 6 | resolv_conf_path | TEXT |
| 7 | hostname_path | TEXT |
| 8 | hosts_path | TEXT |
| 9 | log_path | TEXT |
| 10 | name | TEXT |
| 11 | restart_count | INTEGER |
| 12 | driver | TEXT |
| 13 | platform | TEXT |
| 14 | mount_label | TEXT |
| 15 | process_label | TEXT |
| 16 | host_config | TEXT |
| 17 | mounts | TEXT |
| 18 | config | TEXT |
| 19 | network_settings | TEXT |

### docker_images

| Column index | Column name | type |
| ------------ | --------------- | ------- |
| 0 | id | TEXT |
| 1 | created_at | TEXT |
| 2 | labels | TEXT |
| 3 | parent_id | TEXT |
| 4 | repo_tags | TEXT |
| 5 | repo_digests | TEXT |
| 6 | container_count | INTEGER |
| 7 | shared_size | INTEGER |
| 8 | size | INTEGER |

### docker_networks

| Column index | Column name | type |
| ------------ | ----------- | ------- |
| 0 | id | TEXT |
| 1 | name | TEXT |
| 2 | created_at | TEXT |
| 3 | scope | TEXT |
| 4 | driver | TEXT |
| 5 | enable_ipv6 | INTEGER |
| 6 | ipam | TEXT |
| 7 | containers | TEXT |
| 8 | options | TEXT |
| 9 | labels | TEXT |
| 10 | peers | TEXT |
| 11 | services | TEXT |
| 12 | internal | INTEGER |
| 13 | attachable | INTEGER |
| 14 | ingress | INTEGER |
| 15 | config_only | INTEGER |
| 16 | config_from | TEXT |

## Caveats

- The plugin does not do any caching. Each query will fetch the data from the docker daemon.
- The plugin does not support inserts/updates/deletes.
- Some columns are returned as JSON objects. You can use the `json_extract` or the `->>` operator to extract the data with the json path.
193 changes: 193 additions & 0 deletions plugins/docker/container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package main

import (
"context"
"fmt"

"github.com/julien040/anyquery/rpc"
)

// A constructor to create a new table instance
// This function is called everytime a new connection is made to the plugin
//
// It should return a new table instance, the database schema and if there is an error
func containerCreator(args rpc.TableCreatorArgs) (rpc.Table, *rpc.DatabaseSchema, error) {
return &containerTable{}, &rpc.DatabaseSchema{
Columns: []rpc.DatabaseSchemaColumn{
{
Name: "container_id",
Type: rpc.ColumnTypeString,
IsParameter: true,
IsRequired: true,
},
{
Name: "host",
Type: rpc.ColumnTypeString,
IsParameter: true,
},

{
Name: "id",
Type: rpc.ColumnTypeString,
},
{
Name: "created_at",
Type: rpc.ColumnTypeString,
},
{
Name: "path",
Type: rpc.ColumnTypeString,
},
{
Name: "args",
Type: rpc.ColumnTypeString,
},
{
Name: "container_state",
Type: rpc.ColumnTypeString,
},
{
Name: "image",
Type: rpc.ColumnTypeString,
},
{
Name: "resolv_conf_path",
Type: rpc.ColumnTypeString,
},
{
Name: "hostname_path",
Type: rpc.ColumnTypeString,
},
{
Name: "hosts_path",
Type: rpc.ColumnTypeString,
},
{
Name: "log_path",
Type: rpc.ColumnTypeString,
},
{
Name: "name",
Type: rpc.ColumnTypeString,
},
{
Name: "restart_count",
Type: rpc.ColumnTypeInt,
},
{
Name: "driver",
Type: rpc.ColumnTypeString,
},
{
Name: "platform",
Type: rpc.ColumnTypeString,
},
{
Name: "mount_label",
Type: rpc.ColumnTypeString,
},
{
Name: "process_label",
Type: rpc.ColumnTypeString,
},
{
Name: "host_config",
Type: rpc.ColumnTypeString,
},
{
Name: "mounts",
Type: rpc.ColumnTypeString,
},
{
Name: "config",
Type: rpc.ColumnTypeString,
},
{
Name: "network_settings",
Type: rpc.ColumnTypeString,
},
},
}, nil
}

type containerTable struct {
}

type containerCursor struct {
}

// Return a slice of rows that will be returned to Anyquery and filtered.
// The second return value is true if the cursor has no more rows to return
//
// The constraints are used for optimization purposes to "pre-filter" the rows
// If the rows returned don't match the constraints, it's not an issue. Anyquery will filter them out
func (t *containerCursor) Query(constraints rpc.QueryConstraint) ([][]interface{}, bool, error) {
client, err := createClient(constraints, 1)
if err != nil {
return nil, true, fmt.Errorf("failed to create client: %w", err)
}

containerID := retrieveArgString(constraints, 0)
if containerID == "" {
return nil, true, fmt.Errorf("missing container ID")
}

container, err := client.ContainerInspect(context.Background(), containerID)
if err != nil {
return nil, true, fmt.Errorf("failed to inspect container: %w", err)
}

return [][]interface{}{
{
container.ID,
container.Created,
container.Path,
container.Args,
serializeJSON(container.State),
container.Image,
container.ResolvConfPath,
container.HostnamePath,
container.HostsPath,
container.LogPath,
container.Name,
container.RestartCount,
container.Driver,
container.Platform,
container.MountLabel,
container.ProcessLabel,
serializeJSON(container.HostConfig),
serializeJSON(container.Mounts),
serializeJSON(container.Config),
serializeJSON(container.NetworkSettings),
},
}, true, nil

}

// Create a new cursor that will be used to read rows
func (t *containerTable) CreateReader() rpc.ReaderInterface {
return &containerCursor{}
}

// A slice of rows to insert
func (t *containerTable) Insert(rows [][]interface{}) error {
return nil
}

// A slice of rows to update
// The first element of each row is the primary key
// while the rest are the values to update
// The primary key is therefore present twice
func (t *containerTable) Update(rows [][]interface{}) error {
return nil
}

// A slice of primary keys to delete
func (t *containerTable) Delete(primaryKeys []interface{}) error {
return nil
}

// A destructor to clean up resources
func (t *containerTable) Close() error {
return nil
}
Loading

0 comments on commit dcaedac

Please sign in to comment.