From cd546ba3a6a273432c4b6592cc3f61b89c9da08a Mon Sep 17 00:00:00 2001 From: iamkubi Date: Wed, 17 Apr 2024 20:00:16 -0700 Subject: [PATCH] Add docker disk usage and image prune endpoints * Add Docker disk usage endpoint at /api/system/docker/disk (GET) * Add image prune endpoint at /api/system/docker/image/prune (DELETE) --- environment/docker.go | 10 +++++++ router/router.go | 6 ++-- router/router_system.go | 20 +++++++++++++ system/system.go | 65 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 3 deletions(-) diff --git a/environment/docker.go b/environment/docker.go index 1f61d8f7..b0cea031 100644 --- a/environment/docker.go +++ b/environment/docker.go @@ -105,3 +105,13 @@ func createDockerNetwork(ctx context.Context, cli *client.Client) error { } return nil } + +// Gets usage statistics for the Docker Daemon such as disk usage +func GetDockerUsage(ctx context.Context) (types.DiskUsage, error) { + cli, err := Docker() + if err != nil { + return types.DiskUsage{}, err + } + disk, err := cli.DiskUsage(ctx, types.DiskUsageOptions{}) + return disk, err +} diff --git a/router/router.go b/router/router.go index 743f2b71..b988d109 100644 --- a/router/router.go +++ b/router/router.go @@ -4,7 +4,6 @@ import ( "emperror.dev/errors" "github.com/apex/log" "github.com/gin-gonic/gin" - "github.com/pelican-dev/wings/config" "github.com/pelican-dev/wings/remote" "github.com/pelican-dev/wings/router/middleware" @@ -53,13 +52,14 @@ func Configure(m *wserver.Manager, client remote.Client) *gin.Engine { // This request does not need the AuthorizationMiddleware as the panel should never call it // and requests are authenticated through a JWT the panel issues to the other daemon. router.POST("/api/transfers", postTransfers) - + // All the routes beyond this mount will use an authorization middleware // and will not be accessible without the correct Authorization header provided. protected := router.Use(middleware.RequireAuthorization()) protected.POST("/api/update", postUpdateConfiguration) - protected.GET("/api/system", getSystemInformation) + protected.GET("/api/system/docker/disk", getDockerDiskUsage) + protected.DELETE("/api/system/docker/image/prune", pruneDockerImages) protected.GET("/api/system/ips", getSystemIps) protected.GET("/api/system/utilization", getSystemUtilization) protected.GET("/api/servers", getAllServers) diff --git a/router/router_system.go b/router/router_system.go index ddb97f5a..9520b05a 100644 --- a/router/router_system.go +++ b/router/router_system.go @@ -64,6 +64,26 @@ func getSystemUtilization(c *gin.Context) { c.JSON(http.StatusOK, u) } +// Returns docker disk utilization +func getDockerDiskUsage(c *gin.Context) { + d, err := system.GetDockerDiskUsage(c) + if err != nil { + middleware.CaptureAndAbort(c, err) + return + } + c.JSON(http.StatusOK, d) +} + +// Prunes the docker image cache +func pruneDockerImages(c *gin.Context) { + p, err := system.PruneDockerImages(c) + if err != nil { + middleware.CaptureAndAbort(c, err) + return + } + c.JSON(http.StatusOK, p) +} + // Returns all the servers that are registered and configured correctly on // this wings instance. func getAllServers(c *gin.Context) { diff --git a/system/system.go b/system/system.go index 2130f399..4e37986c 100644 --- a/system/system.go +++ b/system/system.go @@ -2,6 +2,7 @@ package system import ( "context" + "github.com/docker/docker/api/types/filters" "net" "runtime" @@ -77,6 +78,14 @@ type Utilization struct { DiskUsed uint64 `json:"disk_used"` } +type DockerDiskUsage struct { + ContainersSize int64 `json:"containers_size"` + ImagesTotal int `json:"images_total"` + ImagesActive int64 `json:"images_active"` + ImagesSize int64 `json:"images_size"` + BuildCacheSize int64 `json:"build_cache_size"` +} + func GetSystemInformation() (*Information, error) { k, err := kernel.GetKernelVersion() if err != nil { @@ -195,6 +204,62 @@ func GetSystemUtilization() (*Utilization, error) { }, nil } +func GetDockerDiskUsage(ctx context.Context) (*DockerDiskUsage, error) { + // TODO: find a way to re-use the client from the docker environment. + c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return &DockerDiskUsage{}, err + } + defer c.Close() + + d, err := c.DiskUsage(ctx, types.DiskUsageOptions{}) + if err != nil { + return &DockerDiskUsage{}, err + } + + var bcs int64 + for _, bc := range d.BuildCache { + if !bc.Shared { + bcs += bc.Size + } + } + + var a int64 + for _, i := range d.Images { + if i.Containers > 0 { + a++ + } + } + + var cs int64 + for _, b := range d.Containers { + cs += b.SizeRootFs + } + + return &DockerDiskUsage{ + ImagesTotal: len(d.Images), + ImagesActive: a, + ImagesSize: int64(d.LayersSize), + ContainersSize: int64(cs), + BuildCacheSize: bcs, + }, nil +} + +func PruneDockerImages(ctx context.Context) (types.ImagesPruneReport, error) { + // TODO: find a way to re-use the client from the docker environment. + c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return types.ImagesPruneReport{}, err + } + defer c.Close() + + prune, err := c.ImagesPrune(ctx, filters.Args{}) + if err != nil { + return types.ImagesPruneReport{}, err + } + return prune, nil +} + func GetDockerInfo(ctx context.Context) (types.Version, system.Info, error) { // TODO: find a way to re-use the client from the docker environment. c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())