diff --git a/cmd/sourced/cmd/prune.go b/cmd/sourced/cmd/prune.go index e44cc50..78b0fcf 100644 --- a/cmd/sourced/cmd/prune.go +++ b/cmd/sourced/cmd/prune.go @@ -8,7 +8,7 @@ import ( ) type pruneCmd struct { - Command `name:"prune" short-description:"Stop and remove containers and resources" long-description:"Stops containers and removes containers, networks, and volumes created by 'init' for the current working directory.\nTo delete resources for all working directories pass --all flag.\nImages are not deleted unless you specify the --images flag."` + Command `name:"prune" short-description:"Stop and remove containers and resources" long-description:"Stops containers and removes containers, networks, volumes and configuration created by 'init' for the current working directory.\nTo delete resources for all working directories pass --all flag.\nImages are not deleted unless you specify the --images flag."` All bool `short:"a" long:"all" description:"Remove containers and resources for all working directories"` Images bool `long:"images" description:"Remove docker images"` @@ -43,7 +43,20 @@ func (c *pruneCmd) pruneActive() error { a = append(a, "--rmi", "all") } - return compose.Run(context.Background(), a...) + if err := compose.Run(context.Background(), a...); err != nil { + return err + } + + dir, err := workdir.Active() + if err != nil { + return err + } + + if err := workdir.Remove(dir); err != nil { + return err + } + + return workdir.UnsetActive() } func init() { diff --git a/cmd/sourced/cmd/workdirs.go b/cmd/sourced/cmd/workdirs.go index 0adfeef..64e9961 100644 --- a/cmd/sourced/cmd/workdirs.go +++ b/cmd/sourced/cmd/workdirs.go @@ -16,11 +16,8 @@ func (c *workdirsCmd) Execute(args []string) error { return err } - active, err := workdir.ActiveRepoDir() - if err != nil { - return err - } - + // ignore errors if active dir doesn't exist or unavailable + active, _ := workdir.ActiveRepoDir() for _, dir := range dirs { if dir == active { fmt.Printf("* %s\n", dir) diff --git a/cmd/sourced/compose/workdir/workdir.go b/cmd/sourced/compose/workdir/workdir.go index a7e9162..d957aad 100644 --- a/cmd/sourced/compose/workdir/workdir.go +++ b/cmd/sourced/compose/workdir/workdir.go @@ -20,6 +20,9 @@ import ( const activeDir = "__active__" +// RequiredFiles list of required files in a directory to treat it as a working directory +var RequiredFiles = []string{".env", "docker-compose.yml"} + // Init creates a working directory in ~/.srcd for the given repositories // directory. The working directory will contain a docker-compose.yml and a // .env file. @@ -124,22 +127,46 @@ func SetActive(reposdir string) error { return os.Symlink(workdir, dir) } +// UnsetActive removes symlink for active workdir +func UnsetActive() error { + dir, err := Active() + if err != nil { + return err + } + + _, err = os.Stat(dir) + if !os.IsNotExist(err) { + err = os.Remove(dir) + if err != nil { + return errors.Wrap(err, "could not delete active workdir directory symlink") + } + } + + return nil +} + // Active returns the absolute path to $HOME/.srcd/workdirs/__active__ func Active() (string, error) { return path(activeDir) } -// ActiveRepoDir return repositories directory for an active working directory -func ActiveRepoDir() (string, error) { - wpath, err := workdirsPath() +// EvalActive returns resolved path to $HOME/.srcd/workdirs/__active__ +func EvalActive() (string, error) { + active, err := Active() if err != nil { return "", err } - active, err := Active() + + return filepath.EvalSymlinks(active) +} + +// ActiveRepoDir return repositories directory for an active working directory +func ActiveRepoDir() (string, error) { + wpath, err := workdirsPath() if err != nil { return "", err } - active, err = filepath.EvalSymlinks(active) + active, err := EvalActive() if err != nil { return "", err } @@ -162,11 +189,13 @@ func List() ([]string, error) { if info.IsDir() { return nil } - if info.Name() == ".env" || info.Name() == "docker-compose.yml" { - if _, ok := dirs[filepath.Dir(path)]; !ok { - dirs[filepath.Dir(path)] = 0 + for _, f := range RequiredFiles { + if info.Name() == f { + if _, ok := dirs[filepath.Dir(path)]; !ok { + dirs[filepath.Dir(path)] = 0 + } + dirs[filepath.Dir(path)]++ } - dirs[filepath.Dir(path)]++ } return nil }) @@ -176,7 +205,7 @@ func List() ([]string, error) { res := make([]string, 0) for dir, files := range dirs { - if files != 2 { + if files != len(RequiredFiles) { continue } res = append(res, dir) @@ -243,3 +272,37 @@ func stripBase(base, target string) (string, error) { return filepath.Join("/", p), nil } + +// Remove removes working directory by removing required files +// and recursively removes directories up to the workdirs root as long as they are empty +func Remove(path string) error { + workdirsRoot, err := workdirsPath() + if err != nil { + return err + } + + for _, f := range RequiredFiles { + if err := os.Remove(filepath.Join(path, f)); err != nil { + return errors.Wrap(err, "could not remove from workdir directory") + } + } + + for { + files, err := ioutil.ReadDir(path) + if err != nil { + return errors.Wrap(err, "could not read workdir directory") + } + if len(files) > 0 { + return nil + } + + if err := os.Remove(path); err != nil { + return errors.Wrap(err, "could not delete workdir directory") + } + + path = filepath.Dir(path) + if path == workdirsRoot { + return nil + } + } +}