Skip to content

Commit

Permalink
Merge pull request #3 from plesk/fancy-ui
Browse files Browse the repository at this point in the history
Fancy UI
  • Loading branch information
avoronkov committed Mar 17, 2021
2 parents aeff60e + bf98522 commit 6cbfa07
Show file tree
Hide file tree
Showing 11 changed files with 429 additions and 40 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,7 @@ Creating of new files/directories, setting attributes is going to be done later.
- Mkdir and file crating support.

- Other FS features...

- Daemonization

-
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ go 1.13

require (
github.com/hanwen/go-fuse/v2 v2.0.2
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/manifoldco/promptui v0.8.0
github.com/sevlyar/go-daemon v0.1.5
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c // indirect
)
23 changes: 23 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,33 @@
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc=
github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
github.com/hanwen/go-fuse/v2 v2.0.2 h1:BtsqKI5RXOqDMnTgpCb0IWgvRgGLJdqYVZ/Hm6KgKto=
github.com/hanwen/go-fuse/v2 v2.0.2/go.mod h1:HH3ygZOoyRbP9y2q7y3+JM6hPL+Epe29IbWaS0UA81o=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo=
github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+Qk=
github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c h1:jceGD5YNJGgGMkJz79agzOln1K9TaZUjv5ird16qniQ=
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
17 changes: 17 additions & 0 deletions lib/dockerfs/container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dockerfs

import (
"fmt"
"strings"
)

type Container struct {
Id string
Names []string
Image string
Command string
}

func (c *Container) String() string {
return fmt.Sprintf("%v %v (from %v): %v", c.Id[:8], strings.Join(c.Names, ", "), c.Image, c.Command)
}
22 changes: 22 additions & 0 deletions lib/dockerfs/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"path/filepath"
"strings"
Expand All @@ -26,6 +27,9 @@ type dockerMng interface {

// Save file
SaveFile(path string, data []byte, stat *ContainerPathStat) (err error)

// List containers
ContainersList() ([]Container, error)
}

var _ = (dockerMng)((*dockerMngImpl)(nil))
Expand Down Expand Up @@ -100,6 +104,24 @@ func (d *dockerMngImpl) GetFile(path string) (io.ReadCloser, error) {
}, nil
}

func (d *dockerMngImpl) ContainersList() ([]Container, error) {
url := "/containers/json"
resp, err := d.httpc.Get(url)
if err != nil {
return nil, fmt.Errorf("Get request to %q failed: %w", url, err)
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var cts []Container
if err := json.Unmarshal(data, &cts); err != nil {
return nil, err
}
return cts, nil
}

type readCloser struct {
reader io.Reader
close func() error
Expand Down
4 changes: 4 additions & 0 deletions lib/dockerfs/docker_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,7 @@ func (d *dockerMngMock) SaveFile(path string, data []byte, stat *ContainerPathSt
_, err = f.Write(data)
return err
}

func (d *dockerMngMock) ContainersList() ([]Container, error) {
return nil, nil
}
36 changes: 36 additions & 0 deletions lib/manager/container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package manager

import (
"fmt"
"strings"

"github.com/plesk/docker-fs/lib/dockerfs"
)

type Container struct {
Id string
Names []string
Image string
Command string

//
MountPoint string
Mounted bool
ShortId string
Name string
}

func FromContainer(c *dockerfs.Container) Container {
return Container{
Id: c.Id,
Names: c.Names,
Image: c.Image,
Command: c.Command,
ShortId: c.Id[:8],
Name: strings.TrimLeft(c.Names[0], "/"),
}
}

func (c *Container) String() string {
return fmt.Sprintf("%v %v (from %v): %v", c.Id[:8], strings.Join(c.Names, ", "), c.Image, c.Command)
}
166 changes: 166 additions & 0 deletions lib/manager/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package manager

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"os/signal"
"path/filepath"
"syscall"

"github.com/plesk/docker-fs/lib/log"

"github.com/plesk/docker-fs/lib/dockerfs"

"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"

daemon "github.com/sevlyar/go-daemon"
)

type Manager struct {
statusPath string
}

func New() *Manager {
home, err := os.UserHomeDir()
if err != nil {
log.Printf("[warning] Cannot detect user home directory. Use /tmp.")
home = "/tmp"
}
return &Manager{
statusPath: filepath.Join(home, ".dockerfs.status.json"),
}
}

func (m *Manager) ListContainers() ([]Container, error) {
httpc, err := dockerfs.NewClient("unix:/var/run/docker.sock")
if err != nil {
return nil, err
}
dmng := dockerfs.NewDockerMng(httpc, "")
list, err := dmng.ContainersList()
if err != nil {
return nil, err
}
status, err := m.readStatus()
if err != nil {
return nil, err
}
var result []Container
for _, c := range list {
ct := FromContainer(&c)
if s, ok := status[ct.Id]; ok {
ct.MountPoint = s
ct.Mounted = ct.MountPoint != ""
}
result = append(result, ct)
}
return result, nil
}

func (m *Manager) MountContainer(containerId, mountPoint string, daemonize bool) error {
if err := m.writeStatus(containerId, mountPoint); err != nil {
return err
}

if daemonize {
ctx := daemon.Context{}
child, err := ctx.Reborn()
if err != nil {
return fmt.Errorf("Daemonization failed: %w", err)
}
if child != nil {
// parent process
return nil
}
}

log.Printf("[info] Check if mount directory exists (%v)...", mountPoint)
if err := os.MkdirAll(mountPoint, 0755); err != nil {
return err
}
log.Printf("[info] Fetching content of container %v...", containerId)
dockerMng := dockerfs.NewMng(containerId)
if err := dockerMng.Init(); err != nil {
return fmt.Errorf("dockerMng.Init() failed: %w", err)
}

root := dockerMng.Root()

log.Printf("[info] Mounting FS to %v...", mountPoint)
server, err := fs.Mount(mountPoint, root, &fs.Options{})
if err != nil {
return fmt.Errorf("Mount failed: %w", err)
}

log.Printf("[info] Setting up signal handler...")
osSignalChannel := make(chan os.Signal, 1)
signal.Notify(osSignalChannel, syscall.SIGTERM, syscall.SIGINT)
go shutdown(server, osSignalChannel)

log.Printf("[info] OK!")
server.Wait()
log.Printf("[info] Server finished.")

return m.writeStatus(containerId, "")
}

func (m *Manager) UnmountContainer(id, path string) error {
cmd := exec.Command("umount", path)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
_ = cmd.Run()
return m.writeStatus(id, "")
}

func (m *Manager) writeStatus(id, path string) error {
fmt.Printf("write status: %q = %q\n", id, path)
status, err := m.readStatus()
if err != nil {
return err
}
if path != "" {
absPath, err := filepath.Abs(path)
if err != nil {
return err
}
status[id] = absPath
} else {
delete(status, id)
}
data, err := json.Marshal(status)
if err != nil {
return err
}
fmt.Printf("status => %s\n", data)
return ioutil.WriteFile(m.statusPath, data, 0644)
}

func (m *Manager) readStatus() (map[string]string, error) {
data, err := ioutil.ReadFile(m.statusPath)
if os.IsNotExist(err) {
return map[string]string{}, nil
}
if err != nil {
return nil, err
}
status := map[string]string{}
if err := json.Unmarshal(data, &status); err != nil {
return nil, err
}
return status, nil
}

func shutdown(server *fuse.Server, signals <-chan os.Signal) {
<-signals
if err := server.Unmount(); err != nil {
log.Printf("[warning] server unmount failed: %v", err)
os.Exit(1)
}

log.Printf("[info] Unmount successful.")
os.Exit(0)
}
24 changes: 24 additions & 0 deletions lib/tui/templates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package tui

import (
"github.com/manifoldco/promptui"
)

var listTemplates = &promptui.SelectTemplates{
Label: "Select container to mount/unmount. {{ \"(use ^C to exit)\" | faint }}",
Active: "\U0000261E {{ if .Mounted }}{{ .ShortId | blue | bold }} {{ .Name | blue | bold }} (mounted){{ else }}{{ .ShortId | bold }} {{ .Name | bold }}{{ end }}",
Inactive: " {{ if .Mounted }}{{ .ShortId | blue }} {{ .Name | blue }} (mounted){{else}}{{ .ShortId }} {{ .Name }}{{ end }}",
Details: `
------ Container ------
Id: {{ .Id }}
Name: {{ .Names }}
Image: {{ .Image }}
Command: {{ .Command }}
{{ if .Mounted }}MountPoint: {{ .MountPoint }}{{ end }}`,
}

var confirmUnmountTemplates = &promptui.SelectTemplates{
Label: "{{ \"Unmount\" | red }} container {{ .Id | bold}} from {{ .Mp | bold }}",
Active: "\U0000261E {{ . | bold }}",
Inactive: " {{ . }}",
}
Loading

0 comments on commit 6cbfa07

Please sign in to comment.