package app import ( "context" "strconv" "strings" "time" "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/recipe" stack "coopcloud.tech/abra/pkg/upstream/stack" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" dockerClient "github.com/docker/docker/client" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) var appErrorsCommand = cli.Command{ Name: "errors", Usage: "List errors for a deployed app", Description: ` This command lists errors for a deployed app. This is a best-effort implementation and an attempt to gather a number of tips & tricks for finding errors together into one convenient command. When an app is failing to deploy or having issues, it could be a lot of things. This command currently takes into account: Is the service deployed? Is the service killed by an OOM error? Is the service reporting an error (like in "ps --no-trunc" output) Is the service healthcheck failing? what are the healthcheck logs? Got any more ideas? Please let us know: https://git.coopcloud.tech/coop-cloud/organising/issues/new/choose This command is best accompanied by "abra app logs " which may reveal further information which can help you debug the cause of an app failure via the logs. `, Aliases: []string{"e"}, Flags: []cli.Flag{ internal.DebugFlag, internal.NoInputFlag, internal.WatchFlag, }, Before: internal.SubCommandBefore, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { app := internal.ValidateApp(c) cl, err := client.New(app.Server) if err != nil { logrus.Fatal(err) } isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName()) if err != nil { logrus.Fatal(err) } if !isDeployed { logrus.Fatalf("%s is not deployed?", app.Name) } if !internal.Watch { if err := checkErrors(c, cl, app); err != nil { logrus.Fatal(err) } return nil } for { if err := checkErrors(c, cl, app); err != nil { logrus.Fatal(err) } time.Sleep(2 * time.Second) } return nil }, } func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App) error { recipe, err := recipe.Get(app.Type) if err != nil { return err } for _, service := range recipe.Config.Services { filters := filters.NewArgs() filters.Add("name", service.Name) containers, err := cl.ContainerList(context.Background(), types.ContainerListOptions{Filters: filters}) if err != nil { return err } if len(containers) == 0 { logrus.Warnf("%s is not up, something seems wrong", service.Name) continue } container := containers[0] containerState, err := cl.ContainerInspect(context.Background(), container.ID) if err != nil { logrus.Fatal(err) } if containerState.State.OOMKilled { logrus.Warnf("%s has been killed due to an out of memory error", service.Name) } if containerState.State.Error != "" { logrus.Warnf("%s reports this error: %s", service.Name, containerState.State.Error) } if containerState.State.Health != nil { if containerState.State.Health.Status != "healthy" { logrus.Warnf("%s healthcheck status is %s", service.Name, containerState.State.Health.Status) logrus.Warnf("%s healthcheck has failed %s times", service.Name, strconv.Itoa(containerState.State.Health.FailingStreak)) for _, log := range containerState.State.Health.Log { logrus.Warnf("%s healthcheck logs: %s", service.Name, strings.TrimSpace(log.Output)) } } } } return nil } func getServiceName(names []string) string { containerName := strings.Join(names, " ") trimmed := strings.TrimPrefix(containerName, "/") return strings.Split(trimmed, ".")[0] }