Adding server prune and undeploy prune #278

Merged
decentral1se merged 2 commits from codegod100/abra:prune into main 2023-02-17 08:53:47 +00:00
3 changed files with 142 additions and 3 deletions

View File

@ -2,15 +2,70 @@ package app
import (
"context"
"fmt"
"time"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/docker/docker/api/types/filters"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
var prune bool
var pruneFlag = &cli.BoolFlag{
Name: "prune, p",
Destination: &prune,
codegod100 marked this conversation as resolved
Review

stack 👉 an app

`stack` 👉 `an app`
Usage: "Prunes unused containers, networks, and dangling images for an app",
}
func cleanup(c *cli.Context) error {
for {
if !prune {
return nil
}
app := internal.ValidateApp(c)
stackName := app.StackName()
cl, err := client.New(app.Server)
if err != nil {
logrus.Fatal(err)
}
ctx := context.Background()
pruneFilters := filters.NewArgs()
stackSearch := fmt.Sprintf("%s*", stackName)
pruneFilters.Add("label", stackSearch)
cr, err := cl.ContainersPrune(ctx, pruneFilters)
if err != nil {
logrus.Errorf(err.Error())
time.Sleep(time.Second)
continue
}
logrus.Infof("Containers deleted: %s; Space reclaimed: %v", cr.ContainersDeleted, cr.SpaceReclaimed)
nr, err := cl.NetworksPrune(ctx, pruneFilters)
if err != nil {
logrus.Errorf(err.Error())
time.Sleep(time.Second)
continue
}
logrus.Infof("Networks deleted %s", nr.NetworksDeleted)
ir, err := cl.ImagesPrune(ctx, pruneFilters)
if err != nil {
logrus.Errorf(err.Error())
time.Sleep(time.Second)
continue
}
logrus.Infof("Images deleted: %s; Space reclaimed: %v", ir.ImagesDeleted, ir.SpaceReclaimed)
break
}
return nil
}
var appUndeployCommand = cli.Command{
Name: "undeploy",
codegod100 marked this conversation as resolved Outdated

Hmmm, I'm not sure we should be putting this in After as it has some not obvious runtime behaviour (see https://pkg.go.dev/github.com/urfave/cli/v2#AfterFunc) and also the --prune is part of the normal functioning of the sub-command, not a thing to happen after execution? Perhaps abstracting to a function and calling from the main Action?

Hmmm, I'm not sure we should be putting this in `After` as it has some not obvious runtime behaviour (see https://pkg.go.dev/github.com/urfave/cli/v2#AfterFunc) and also the `--prune` is part of the normal functioning of the sub-command, not a thing to happen after execution? Perhaps abstracting to a function and calling from the main `Action`?

this part? it is run even if Action() panics
The issue I was trying to tackle is that pruning while docker container is still running is kinda pointless. when we run abra app undeploy It takes clock time for the container to spin down. maybe we put an arbitrary wait in there? I hate doing that but not sure another way since we are done talking to docker by the time the actual spindown happens.

this part? `it is run even if Action() panics` The issue I was trying to tackle is that pruning while docker container is still running is kinda pointless. when we run `abra app undeploy` It takes clock time for the container to spin down. maybe we put an arbitrary wait in there? I hate doing that but not sure another way since we are done talking to docker by the time the actual spindown happens.

Aha, I see. Could you just poll every second for the stack until it goes away? Then you can drop into the pruning logic after and still keep it all in the main Action code path.

Aha, I see. Could you just poll every second for the stack until it goes away? Then you can drop into the pruning logic after and still keep it all in the main `Action` code path.
Aliases: []string{"un"},
@ -18,18 +73,18 @@ var appUndeployCommand = cli.Command{
Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
pruneFlag,
},
Before: internal.SubCommandBefore,
Usage: "Undeploy an app",
Description: `
This does not destroy any of the application data. However, you should remain
vigilant, as your swarm installation will consider any previously attached
codegod100 marked this conversation as resolved Outdated

Could this be tucked into app.Filters? You might need to add a flag to specify the * logic? Would be then:

fs, err := app.Filters(false, false)
if err != nil {
    logrus.Fatal(err)
}

Could potentially learn from the new runtime package options praxis 🤔 For another day...

Could this be tucked into `app.Filters`? You might need to add a flag to specify the `*` logic? Would be then: ``` fs, err := app.Filters(false, false) if err != nil { logrus.Fatal(err) } ``` Could potentially learn from the new `runtime` package options praxis 🤔 For another day...

I'm kinda confused what you mean here. What are those false,false args being passed to Filters()?

I'm kinda confused what you mean here. What are those `false,false` args being passed to `Filters()`?

Yeh it's kinda a mess not of our making, i'll copy in the comments:

// Filters retrieves exact app filters for querying the container runtime. Due
// to upstream issues, filtering works different depending on what you're
// querying. So, for example, secrets don't work with regex! The caller needs
// to implement their own validation that the right secrets are matched. In
// order to handle these cases, we provide the `appendServiceNames` /
// `exactMatch` modifiers.

Filters goal was to hide away the weirdness. But I see now again that the function arguments are also confusing. Anyway, feel free to skip this for now and I can loop back after for a re-factor.

Yeh it's kinda a mess not of our making, i'll copy in the comments: ``` // Filters retrieves exact app filters for querying the container runtime. Due // to upstream issues, filtering works different depending on what you're // querying. So, for example, secrets don't work with regex! The caller needs // to implement their own validation that the right secrets are matched. In // order to handle these cases, we provide the `appendServiceNames` / // `exactMatch` modifiers. ``` `Filters` goal was to hide away the weirdness. But I see now again that the function arguments are also confusing. Anyway, feel free to skip this for now and I can loop back after for a re-factor.
volumes as eligiblef or pruning once undeployed.
volumes as eligible for pruning once undeployed.
`,
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
stackName := app.StackName()
codegod100 marked this conversation as resolved Outdated

logrus.Fatal(err)

`logrus.Fatal(err)`
cl, err := client.New(app.Server)
if err != nil {
logrus.Fatal(err)
@ -54,7 +109,7 @@ volumes as eligiblef or pruning once undeployed.
if err := stack.RunRemove(context.Background(), cl, rmOpts); err != nil {
logrus.Fatal(err)
}
cleanup(c)
return nil
},
BashComplete: autocomplete.AppNameComplete,

83
cli/server/prune.go Normal file
View File

@ -0,0 +1,83 @@
package server
import (
"context"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/client"
"github.com/docker/docker/api/types/filters"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
var allFilter bool
var allFilterFlag = &cli.BoolFlag{
Name: "all, a",
Usage: "Remove all unused images not just dangling ones",
Destination: &allFilter,
}
var volunesFilter bool
var volumesFilterFlag = &cli.BoolFlag{
Name: "volumes, v",
Usage: "Prune volumes",
Destination: &volunesFilter,
}
var serverPruneCommand = cli.Command{
Name: "prune",
Aliases: []string{"p"},
Usage: "Prune a managed server; Runs a docker system prune",
Description: "Prunes unused containers, networks, and dangling images",
codegod100 marked this conversation as resolved Outdated

Could be a "..." oneliner after all?

Could be a `"..."` oneliner after all?
ArgsUsage: "[<server>]",
Flags: []cli.Flag{
allFilterFlag,
volumesFilterFlag,
internal.DebugFlag,
},
Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error {
// Leaving filters empty for now
var args filters.Args
serverName := internal.ValidateServer(c)
cl, err := client.New(serverName)
if err != nil {
logrus.Fatal(err)
}
ctx := context.Background()
codegod100 marked this conversation as resolved
Review

logrus.Fatal(err)

`logrus.Fatal(err)`
cr, err := cl.ContainersPrune(ctx, args)
if err != nil {
logrus.Fatal(err)
}
logrus.Infof("Containers deleted: %s; Space reclaimed: %v", cr.ContainersDeleted, cr.SpaceReclaimed)
codegod100 marked this conversation as resolved
Review

logrus.Fatal(err)

`logrus.Fatal(err)`
nr, err := cl.NetworksPrune(ctx, args)
if err != nil {
logrus.Fatal(err)
}
logrus.Infof("Networks deleted %s", nr.NetworksDeleted)
codegod100 marked this conversation as resolved
Review

logrus.Fatal(err)

`logrus.Fatal(err)`
pruneFilters := filters.NewArgs()
if allFilter {
pruneFilters.Add("dangling", "false")
}
ir, err := cl.ImagesPrune(ctx, pruneFilters)
if err != nil {
logrus.Fatal(err)
}
logrus.Infof("Images deleted: %s; Space reclaimed: %v", ir.ImagesDeleted, ir.SpaceReclaimed)
codegod100 marked this conversation as resolved
Review

logrus.Fatal(err)

`logrus.Fatal(err)`
if volunesFilter {
vr, err := cl.VolumesPrune(ctx, args)
if err != nil {
logrus.Fatal(err)
}
logrus.Infof("Volumes deleted: %s; Space reclaimed: %v", vr.VolumesDeleted, vr.SpaceReclaimed)
codegod100 marked this conversation as resolved
Review

logrus.Fatal(err)

`logrus.Fatal(err)`
}
return nil
},
}

View File

@ -22,5 +22,6 @@ recipes, see available flags on "abra server add" for more.
serverAddCommand,
serverListCommand,
serverRemoveCommand,
serverPruneCommand,
},
}