Skip to content

Commit

Permalink
added ability to query multiple containers
Browse files Browse the repository at this point in the history
  • Loading branch information
tetra-fox committed May 20, 2022
1 parent 78283dd commit 1964648
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 32 deletions.
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# container-status

A simple API that returns the status of all running Docker containers.
A tiny API written in Go that returns metadata of all Docker containers.

I created this because I wanted a simple way to display the status of the various services that run my home network without displaying sensitive information (such as my network configuration, volume bindings or entrypoint arguments).

You can see a live instance of this [here](https://home.tetra.cool/status)!

Expand All @@ -16,12 +18,12 @@ services:
ports:
- "3621:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/run/docker.sock:/var/run/docker.sock # So we can get information from Docker!
```
# Endpoints
| Method | Endpoint | Description |
| ------ | -------- | ------------------------------------------- |
| GET | / | Returns a list of all running containers. |
| GET | /{name} | Returns the status of a specific container. |
| Method | Endpoint | Description |
| ------ | -------------- | --------------------------------------------------------------------- |
| GET | / | Returns the metadata of all containers. |
| GET | /{name},{name} | Returns the metadata of the specified container(s). (comma-separated) |
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ require (
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/ugorji/go/codec v1.1.7 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
Expand All @@ -79,6 +81,8 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
Expand Down
74 changes: 49 additions & 25 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"context"
"encoding/json"
"net/http"
"strings"
"time"
"golang.org/x/exp/slices"

"github.com/gin-gonic/gin"

Expand All @@ -13,8 +15,13 @@ import (
)

type responseContainer struct {
Name string `json:"name"`
ID string `json:"id"`
PrimaryName string `json:"name"`
Names []string `json:"names"`
State string `json:"state"`
Status string `json:"status"`
Image string `json:"image"`
ImageHash string `json:"image_hash"`
}

type response struct {
Expand All @@ -25,12 +32,13 @@ type response struct {
func main() {
router := gin.Default()
router.GET("/", listContainers)
router.GET("/:name", getContainerByName)
router.GET("/:names", listContainersByName)

router.Run();
}

func getContainers() ([]byte, error) {
// Helpers
func getContainers() ([]responseContainer, error) {
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
Expand All @@ -42,52 +50,68 @@ func getContainers() ([]byte, error) {
return nil, err;
}

sanitizedContainers := make([]responseContainer, len(containers))
output := make([]responseContainer, len(containers))
for i, container := range containers {
sanitizedContainers[i] = responseContainer{container.Names[0][1:], container.State}
output[i] = responseContainer{
ID: container.ID,
State: container.State,
Status: container.Status,
Image: container.Image,
ImageHash: container.ImageID[7:],
}
for _, name := range container.Names {
output[i].Names = append(output[i].Names, strings.TrimPrefix(name, "/"))
}
output[i].PrimaryName = output[i].Names[0]
}

responseJson, err := json.Marshal(response{sanitizedContainers, time.Now().UnixMilli()})
if err != nil {
return nil, err;
}
return output, nil;
}

return responseJson, nil;
func containersToJson(containers []responseContainer) ([]byte, error) {
return json.Marshal(response{Containers: containers, Time: time.Now().UnixMilli()})
}

// Routes
func listContainers(c *gin.Context) {
responseJson, err := getContainers()
containers, err := getContainers()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Data(http.StatusOK, gin.MIMEJSON, responseJson)
}

func getContainerByName(c *gin.Context) {
responseJson, err := getContainers()
responseJson, err := containersToJson(containers);
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

var containers response
err = json.Unmarshal(responseJson, &containers)
c.Data(http.StatusOK, gin.MIMEJSON, responseJson)
}

func listContainersByName(c *gin.Context) {
names := strings.Split(c.Param("names"), ",")

containers, err := getContainers()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

for _, container := range containers.Containers {
if container.Name == c.Param("name") {
responseJson, err := json.Marshal(response{[]responseContainer{container}, time.Now().UnixMilli()})
if err != nil {
panic(err)
filteredContainers := make([]responseContainer, 0)
for _, container := range containers {
for _, name := range names {
if slices.Contains(container.Names, name) {
filteredContainers = append(filteredContainers, container)
}
c.Data(http.StatusOK, gin.MIMEJSON, responseJson)
return
}
}

c.JSON(http.StatusNotFound, gin.H{"error": "container not found"})
responseJson, err := containersToJson(filteredContainers);
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.Data(http.StatusOK, gin.MIMEJSON, responseJson)
}

0 comments on commit 1964648

Please sign in to comment.