From 64ff701cd8a155fccad98044579184b76930d4a1 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 14 Oct 2023 22:36:31 -0400 Subject: [PATCH] fix: clean up local build images immediately after an image is released In some cases, we might hold onto intermediate images until the next deploy. While these images are generally part of newer images - and are therefore cleaned up when the child images are no longer in use - they cruft up the 'docker image ls' output, causing some folks to believe Dokku is not cleaning up images. Refs #6272 --- plugins/builder/functions.go | 25 ------- plugins/builder/src/triggers/triggers.go | 4 ++ plugins/builder/triggers.go | 18 ++++- plugins/common/docker.go | 85 ++++++++++++++---------- 4 files changed, 70 insertions(+), 62 deletions(-) diff --git a/plugins/builder/functions.go b/plugins/builder/functions.go index df7d2f863..6d3e06f57 100644 --- a/plugins/builder/functions.go +++ b/plugins/builder/functions.go @@ -3,7 +3,6 @@ package builder import ( "bytes" "errors" - "fmt" "io/ioutil" "os" "path" @@ -12,30 +11,6 @@ import ( "github.com/dokku/dokku/plugins/common" ) -func listImagesByAppLabel(appName string) ([]string, error) { - command := []string{ - common.DockerBin(), - "image", - "ls", - "--quiet", - "--filter", - fmt.Sprintf("label=com.dokku.app-name=%v", appName), - } - - var stderr bytes.Buffer - listCmd := common.NewShellCmd(strings.Join(command, " ")) - listCmd.ShowOutput = false - listCmd.Command.Stderr = &stderr - b, err := listCmd.Output() - - if err != nil { - return []string{}, errors.New(strings.TrimSpace(stderr.String())) - } - - output := strings.Split(strings.TrimSpace(string(b[:])), "\n") - return output, nil -} - func listImagesByImageRepo(imageRepo string) ([]string, error) { command := []string{ common.DockerBin(), diff --git a/plugins/builder/src/triggers/triggers.go b/plugins/builder/src/triggers/triggers.go index 2205d6cce..9fd5ce538 100644 --- a/plugins/builder/src/triggers/triggers.go +++ b/plugins/builder/src/triggers/triggers.go @@ -50,6 +50,10 @@ func main() { case "post-delete": appName := flag.Arg(0) err = builder.TriggerPostDelete(appName) + case "post-release-builder": + builderType := flag.Arg(0) + appName := flag.Arg(1) + err = builder.TriggerPostReleaseBuilder(builderType, appName) case "report": appName := flag.Arg(0) err = builder.ReportSingleApp(appName, "", "") diff --git a/plugins/builder/triggers.go b/plugins/builder/triggers.go index 2b4cc240b..4465fa503 100644 --- a/plugins/builder/triggers.go +++ b/plugins/builder/triggers.go @@ -133,7 +133,9 @@ func TriggerPostDelete(appName string) error { return err } - imagesByAppLabel, err := listImagesByAppLabel(appName) + imagesByAppLabel, err := common.DockerFilterImages([]string{ + fmt.Sprintf("label=com.dokku.app-name=%s", appName), + }) if err != nil { common.LogWarn(err.Error()) } @@ -149,3 +151,17 @@ func TriggerPostDelete(appName string) error { return nil } + +// TriggerPostReleaseBuilder deletes unused build images +func TriggerPostReleaseBuilder(builderType string, appName string) error { + images, _ := common.DockerFilterImages([]string{ + "label=com.dokku.image-stage=build", + fmt.Sprintf("label=com.dokku.app-name=%s", appName), + }) + + if err := common.RemoveImages(images); err != nil { + common.LogWarn(err.Error()) + } + + return nil +} diff --git a/plugins/common/docker.go b/plugins/common/docker.go index 9393a4a6c..abff46043 100644 --- a/plugins/common/docker.go +++ b/plugins/common/docker.go @@ -300,17 +300,58 @@ func IsImageHerokuishBased(image string, appName string) bool { // ListDanglingImages lists all dangling image ids for a given app func ListDanglingImages(appName string) ([]string, error) { + filters := []string{"dangling=true"} + if appName != "" { + filters = append(filters, []string{fmt.Sprintf("label=com.dokku.app-name=%v", appName)}...) + } + return DockerFilterImages(filters) +} + +// RemoveImages removes images by ID +func RemoveImages(imageIDs []string) error { + if len(imageIDs) == 0 { + return nil + } + command := []string{ DockerBin(), "image", - "ls", - "--quiet", - "--filter", - "dangling=true", + "rm", } - if appName != "" { - command = append(command, []string{"--filter", fmt.Sprintf("label=com.dokku.app-name=%v", appName)}...) + command = append(command, imageIDs...) + + var stderr bytes.Buffer + rmCmd := NewShellCmd(strings.Join(command, " ")) + rmCmd.ShowOutput = false + rmCmd.Command.Stderr = &stderr + rmCmd.Execute() + if _, err := rmCmd.Output(); err != nil { + return errors.New(strings.TrimSpace(stderr.String())) + } + + return nil +} + +// VerifyImage returns true if docker image exists in local repo +func VerifyImage(image string) bool { + imageCmd := NewShellCmd(strings.Join([]string{DockerBin(), "image", "inspect", image}, " ")) + imageCmd.ShowOutput = false + return imageCmd.Execute() +} + +// DockerFilterContainers returns a slice of container IDs based on the passed in filters +func DockerFilterContainers(filters []string) ([]string, error) { + command := []string{ + DockerBin(), + "container", + "ls", + "--quiet", + "--all", + } + + for _, filter := range filters { + command = append(command, "--filter", filter) } var stderr bytes.Buffer @@ -327,39 +368,11 @@ func ListDanglingImages(appName string) ([]string, error) { return output, nil } -// RemoveImages removes images by ID -func RemoveImages(imageIDs []string) { - if len(imageIDs) == 0 { - return - } - +// DockerFilterImages returns a slice of image IDs based on the passed in filters +func DockerFilterImages(filters []string) ([]string, error) { command := []string{ DockerBin(), "image", - "rm", - } - - command = append(command, imageIDs...) - - var stderr bytes.Buffer - rmCmd := NewShellCmd(strings.Join(command, " ")) - rmCmd.ShowOutput = false - rmCmd.Command.Stderr = &stderr - rmCmd.Execute() -} - -// VerifyImage returns true if docker image exists in local repo -func VerifyImage(image string) bool { - imageCmd := NewShellCmd(strings.Join([]string{DockerBin(), "image", "inspect", image}, " ")) - imageCmd.ShowOutput = false - return imageCmd.Execute() -} - -// DockerFilterContainers returns a slice of container IDs based on the passed in filters -func DockerFilterContainers(filters []string) ([]string, error) { - command := []string{ - DockerBin(), - "container", "ls", "--quiet", "--all",