From 19646489dca05097533741644c0c136ba8c99cbb Mon Sep 17 00:00:00 2001 From: Tetra Date: Thu, 19 May 2022 20:32:04 -0400 Subject: [PATCH] added ability to query multiple containers --- README.md | 14 ++++++----- go.mod | 3 ++- go.sum | 4 +++ main.go | 74 ++++++++++++++++++++++++++++++++++++------------------- 4 files changed, 63 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index b3b912f..cb3044b 100644 --- a/README.md +++ b/README.md @@ -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)! @@ -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) | diff --git a/go.mod b/go.mod index b8b9205..8085361 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index e75f99a..c3a40c1 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= diff --git a/main.go b/main.go index 36f90b4..e3b55ff 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,9 @@ import ( "context" "encoding/json" "net/http" + "strings" "time" + "golang.org/x/exp/slices" "github.com/gin-gonic/gin" @@ -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 { @@ -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 { @@ -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) } \ No newline at end of file