fix: remove all containers and images by label on app destroy

Previously, we would filter containers and images by name, which could be fail if the app was renamed or no longer was tagged "correctly" due to a rebuild. Now, we filter by label, ensuring an app is completely cleaned up on deletion.

Also:

- Move docker image cleanup from apps plugin to builder plugin: This isn't where it belongs - images should be cleaned up in a builder plugin (or the builders plugin) and not in the apps plugin, which has nothing to do with docker images anyways.
- Remove the code from the scheduler plugin, as that will only do anything if the scheduler is enabled for the app.
- Bypass image removal if there are no images to remove
- Always delete app containers: The herokuish builder creates intermediate build containers - something that should be refactored - and ideally this gets run in that builder plugin, but keeping the logic here ensures we don't have code duplication. This is otherwise safe as it will be a no-op if there are no containers to clean up.
This commit is contained in:
Jose Diaz-Gonzalez
2023-07-01 03:22:01 -04:00
parent 3c5a98cb1b
commit 5dc2f20432
6 changed files with 78 additions and 79 deletions

View File

@@ -1,7 +1,6 @@
package apps
import (
"bytes"
"errors"
"fmt"
"os"
@@ -95,53 +94,6 @@ func destroyApp(appName string) error {
return nil
}
func listImagesByAppLabel(appName string) ([]string, error) {
command := []string{
common.DockerBin(),
"image",
"list",
"--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(),
"image",
"list",
"--quiet",
imageRepo,
}
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
}
// creates an app if allowed
func maybeCreateApp(appName string) error {
if err := appExists(appName); err == nil {

View File

@@ -101,19 +101,5 @@ func TriggerPostDelete(appName string) error {
common.LogWarn(err.Error())
}
imagesByAppLabel, err := listImagesByAppLabel(appName)
if err != nil {
common.LogWarn(err.Error())
}
imageRepo := common.GetAppImageRepo(appName)
imagesByRepo, err := listImagesByImageRepo(imageRepo)
if err != nil {
common.LogWarn(err.Error())
}
images := append(imagesByAppLabel, imagesByRepo...)
common.RemoveImages(images)
return nil
}

View File

@@ -1,11 +1,64 @@
package builder
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"github.com/dokku/dokku/plugins/common"
)
func listImagesByAppLabel(appName string) ([]string, error) {
command := []string{
common.DockerBin(),
"image",
"list",
"--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(),
"image",
"list",
"--quiet",
imageRepo,
}
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 removeAllContents(basePath string) error {
dir, err := ioutil.ReadDir(basePath)
if err != nil {

View File

@@ -129,5 +129,23 @@ func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error {
// TriggerPostDelete destroys the builder property for a given app container
func TriggerPostDelete(appName string) error {
return common.PropertyDestroy("builder", appName)
if err := common.PropertyDestroy("builder", appName); err != nil {
return err
}
imagesByAppLabel, err := listImagesByAppLabel(appName)
if err != nil {
common.LogWarn(err.Error())
}
imageRepo := common.GetAppImageRepo(appName)
imagesByRepo, err := listImagesByImageRepo(imageRepo)
if err != nil {
common.LogWarn(err.Error())
}
images := append(imagesByAppLabel, imagesByRepo...)
common.RemoveImages(images)
return nil
}

View File

@@ -329,6 +329,10 @@ func ListDanglingImages(appName string) ([]string, error) {
// RemoveImages removes images by ID
func RemoveImages(imageIDs []string) {
if len(imageIDs) == 0 {
return
}
command := []string{
DockerBin(),
"image",

View File

@@ -12,23 +12,9 @@ trigger-scheduler-docker-local-post-delete() {
fn-plugin-property-destroy "scheduler-docker-local" "$APP"
rm -rf "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP"
local DOKKU_SCHEDULER=$(get_app_scheduler "$APP")
if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then
return
fi
local IMAGE_REPO=$(get_app_image_repo "$APP")
# remove all application containers & images
# remove all application containers
# shellcheck disable=SC2046
local DOKKU_APP_CIDS=$("$DOCKER_BIN" container list --all --no-trunc | grep "dokku/${APP}:" | awk '{ print $1 }' | xargs)
if [[ -n "$DOKKU_APP_CIDS" ]]; then
# shellcheck disable=SC2086
"$DOCKER_BIN" container rm --force $DOKKU_APP_CIDS >/dev/null 2>&1 || true
fi
# shellcheck disable=SC2046
"$DOCKER_BIN" image remove $("$DOCKER_BIN" image list --quiet "$IMAGE_REPO" | xargs) &>/dev/null || true
"$DOCKER_BIN" container ls --filter "label=com.dokku.app-name=${APP}" -q | xargs -n1 -I {} "$DOCKER_BIN" container rm --force {} &>/dev/null || true
}
trigger-scheduler-docker-local-post-delete "$@"